diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp
index 7851bf9d5f..41f3877777 100644
--- a/indra/newview/fsfloaterposer.cpp
+++ b/indra/newview/fsfloaterposer.cpp
@@ -307,6 +307,12 @@ void FSFloaterPoser::onPoseFileSelect()
LLStringExplicit name = LLStringExplicit(poseName);
mPoseSaveNameEditor->setEnabled(enableButtons);
mPoseSaveNameEditor->setText(name);
+
+ bool isDeltaSave = !poseFileStartsFromTeePose(name);
+ if (isDeltaSave)
+ mLoadPosesBtn->setLabel("Load Diff");
+ else
+ mLoadPosesBtn->setLabel("Load Pose");
}
void FSFloaterPoser::onClickPoseSave()
@@ -348,13 +354,13 @@ 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);
+ bool savingDiff = !mPoserAnimator.posingStartedFromZeroRotations(avatar);
LLSD record;
record["version"]["value"] = (S32)4;
- record["startFromTeePose"]["value"] = fromZeroRot;
+ record["startFromTeePose"]["value"] = !savingDiff;
- LLVector3 rotation, position, scale;
+ LLVector3 rotation, position, scale, zeroVector;
for (const FSPoserAnimator::FSPoserJoint& pj : mPoserAnimator.PoserJoints)
{
@@ -362,8 +368,13 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
if (!mPoserAnimator.tryGetJointSaveVectors(avatar, pj, &rotation, &position, &scale))
continue;
- record[bone_name] = pj.jointName();
- record[bone_name]["enabled"] = mPoserAnimator.isPosingAvatarJoint(avatar, pj);
+ bool jointRotPosScaleAllZero = rotation == zeroVector && position == zeroVector && scale == zeroVector;
+ bool posingThisJoint = mPoserAnimator.isPosingAvatarJoint(avatar, pj);
+ if (savingDiff && (!posingThisJoint || jointRotPosScaleAllZero))
+ continue;
+
+ record[bone_name] = bone_name;
+ record[bone_name]["enabled"] = posingThisJoint;
record[bone_name]["rotation"] = rotation.getValue();
record[bone_name]["position"] = position.getValue();
record[bone_name]["scale"] = scale.getValue();
@@ -494,17 +505,7 @@ void FSFloaterPoser::onClickRecaptureSelectedBones()
if (currentlyPosing)
continue;
- LLVector3 newRotation = mPoserAnimator.getJointRotation(avatar, *item, getJointTranslation(item->jointName()),
- getJointNegation(item->jointName()), CURRENTROTATION);
- LLVector3 newPosition = mPoserAnimator.getJointPosition(avatar, *item, true);
- LLVector3 newScale = mPoserAnimator.getJointScale(avatar, *item, true);
-
- mPoserAnimator.setPosingAvatarJoint(avatar, *item, true);
-
- mPoserAnimator.setJointRotation(avatar, item, newRotation, NONE, getJointTranslation(item->jointName()),
- getJointNegation(item->jointName()));
- mPoserAnimator.setJointPosition(avatar, item, newPosition, NONE);
- mPoserAnimator.setJointScale(avatar, item, newScale, NONE);
+ mPoserAnimator.recaptureJoint(avatar, *item, getJointTranslation(item->jointName()), getJointNegation(item->jointName()));
}
refreshRotationSliders();
@@ -674,6 +675,51 @@ void FSFloaterPoser::onClickLoadHandPose(bool isRightHand)
}
+bool FSFloaterPoser::poseFileStartsFromTeePose(const std::string& poseFileName)
+{
+ std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY);
+ if (!gDirUtilp->fileExists(pathname))
+ return false;
+
+ std::string fullPath =
+ gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY, poseFileName + POSE_INTERNAL_FORMAT_FILE_EXT);
+
+ try
+ {
+ LLSD pose;
+ llifstream infile;
+ bool startFromZeroRot = false;
+
+ infile.open(fullPath);
+ if (!infile.is_open())
+ return false;
+
+ S32 lineCount = LLSDSerialize::fromXML(pose, infile);
+ if (lineCount == LLSDParser::PARSE_FAILURE)
+ {
+ LL_WARNS("Posing") << "Failed to parse file: " << poseFileName << LL_ENDL;
+ return startFromZeroRot;
+ }
+
+ for (LLSD::map_const_iterator itr = pose.beginMap(); itr != pose.endMap(); ++itr)
+ {
+ std::string const& name = itr->first;
+ LLSD const& control_map = itr->second;
+
+ if (name == "startFromTeePose")
+ startFromZeroRot = control_map["value"].asBoolean();
+ }
+
+ return startFromZeroRot;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS("Posing") << "Unable to load or parse the pose: " << poseFileName << " exception: " << e.what() << LL_ENDL;
+ }
+
+ return false;
+}
+
void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod)
{
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY);
@@ -732,7 +778,7 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
if (version > 3)
loadPositionsAndScalesAsDeltas = true;
- if (startFromZeroRot) // old save formats will always start from T-Pose, for better or worse.
+ if (startFromZeroRot) // legacy saves will always start from T-Pose, for better or worse.
{
disableRecapture();
mPoserAnimator.setAllAvatarStartingRotationsToZero(avatar);
@@ -772,7 +818,7 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
}
}
}
- }
+ }
catch ( const std::exception & e )
{
LL_WARNS("Posing") << "Everything caught fire trying to load the pose: " << poseFileName << " exception: " << e.what() << LL_ENDL;
diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h
index 198d6097ba..24b69c6298 100644
--- a/indra/newview/fsfloaterposer.h
+++ b/indra/newview/fsfloaterposer.h
@@ -202,6 +202,7 @@ class FSFloaterPoser : public LLFloater
void onClickBrowsePoseCache();
void onPoseMenuAction(const LLSD& param);
void loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod);
+ bool poseFileStartsFromTeePose(const std::string& poseFileName);
void setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName();
void setUiSelectedAvatarSaveFileName(const std::string& saveFileName);
void showOrHideAdvancedSaveOptions();
diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp
index 4c9bb067aa..4f70b19a2f 100644
--- a/indra/newview/fsposeranimator.cpp
+++ b/indra/newview/fsposeranimator.cpp
@@ -440,6 +440,40 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar)
posingMotion->setAllRotationsToZero();
}
+void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation)
+{
+ LLVector3 newRotation = getJointRotation(avatar, joint, translation, negation, CURRENTROTATION);
+ LLVector3 newPosition = getJointPosition(avatar, joint, true);
+ LLVector3 newScale = getJointScale(avatar, joint, true);
+
+ setPosingAvatarJoint(avatar, joint, true);
+ setStartingJointRotation(avatar, &joint, newRotation, translation, negation);
+
+ // recapture of positions and scale does not reset starting values, since this this could result in unwanted residue deformation after posing stops.
+ setJointPosition(avatar, &joint, newPosition, NONE);
+ setJointScale(avatar, &joint, newScale, NONE);
+}
+
+void FSPoserAnimator::setStartingJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& rotation,
+ E_BoneAxisTranslation translation, S32 negation)
+{
+ if (!isAvatarSafeToUse(avatar))
+ return;
+ if (!joint)
+ return;
+
+ FSPosingMotion* posingMotion = getPosingMotion(avatar);
+ if (!posingMotion)
+ return;
+
+ FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName());
+ if (!jointPose)
+ return;
+
+ LLQuaternion rot_quat = translateRotationToQuaternion(translation, negation, rotation);
+ jointPose->setJointStartRotations(rot_quat);
+}
+
LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation, E_BoneRotationType rotType) const
{
LLVector3 vec3;
diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h
index 1c830e250e..d181c847a8 100644
--- a/indra/newview/fsposeranimator.h
+++ b/indra/newview/fsposeranimator.h
@@ -32,7 +32,7 @@
///
/// Describes how we will cluster the joints/bones/thingos.
-/// Each joint/bone/thingo should have one of these, .
+/// Each joint/bone/thingo should have one of these, FSPoserAnimator.PoserJoints.
///
typedef enum E_BoneTypes
{
@@ -446,7 +446,7 @@ public:
/// The avatar whose joint is being queried.
/// The joint to determine the rotation for.
/// The joint to determine the rotation for.
- /// The style of negation to apply to the set.
+ /// The style of negation to dis-apply to the get.
/// 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, E_BoneRotationType rotType) const;
@@ -459,6 +459,7 @@ public:
/// The rotation to set the joint to.
/// Any ancilliary action to be taken with the change to be made.
/// The axial translation form the supplied joint.
+ /// The style of negation to apply to the set.
void setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& rotation, E_BoneDeflectionStyles style,
E_BoneAxisTranslation translation, S32 negation);
@@ -492,6 +493,15 @@ public:
/// The avatar whose pose should flip left-right.
void flipEntirePose(LLVOAvatar* avatar);
+ ///
+ /// Recaptures the rotation, position and scale state of the supplied joint for the supplied avatar.
+ ///
+ /// The avatar whose joint is to be recaptured.
+ /// The joint to recapture.
+ /// The axial translation form the supplied joint.
+ /// The style of negation to apply to the recapture.
+ void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation);
+
///
/// Sets all of the joint rotations of the supplied avatar to zero.
///
@@ -533,8 +543,8 @@ public:
/// 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.
+ /// Whether the joint already has a rotation because some animation is playing,
+ /// or whether its rotation is zero, the result is always the same: just 'add' the supplied rotation to the existing rotation.
///
void loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, LLVector3 rotation);
@@ -549,7 +559,7 @@ public:
/// 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.
+ /// A delta-save thus accurately reflects what the user changed, and not what the original pose is.
/// 'Legacy' (pre save format version-4) poses we expect to load as absolutes.
///
void loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position);
@@ -612,6 +622,9 @@ public:
/// True if the avatar is safe to manipulate, otherwise false.
bool isAvatarSafeToUse(LLVOAvatar* avatar) const;
+ void setStartingJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& rotation,
+ E_BoneAxisTranslation translation, S32 negation);
+
///
/// 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 718b5d25ca..f0f37a1455 100644
--- a/indra/newview/fsposingmotion.cpp
+++ b/indra/newview/fsposingmotion.cpp
@@ -269,19 +269,20 @@ bool FSPosingMotion::allStartingRotationsAreZero() const
void FSPosingMotion::setAllRotationsToZero()
{
+ LLQuaternion zeroQuat;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
if (poserJoint_iter->isCollisionVolume())
continue;
- poserJoint_iter->setRotationsToZero();
+ poserJoint_iter->setJointStartRotations(zeroQuat);
}
}
constexpr size_t MaximumUndoQueueLength = 20;
///
-/// The constant time interval, in seconds,
+/// The constant time interval, in seconds, specifying whether an 'undo' value should be added.
///
constexpr std::chrono::duration UndoUpdateInterval = std::chrono::duration(0.3);
@@ -517,7 +518,7 @@ void FSPosingMotion::FSJointPose::revertCollisionVolume()
joint->setScale(mBeginningScale);
}
-void FSPosingMotion::FSJointPose::setRotationsToZero() { mBeginningRotation = mTargetRotation = LLQuaternion(); }
+void FSPosingMotion::FSJointPose::setJointStartRotations(LLQuaternion quat) { mBeginningRotation = mTargetRotation = quat; }
FSPosingMotion::FSJointPose::FSJointPose(LLJoint* joint, bool isCollisionVolume)
{
diff --git a/indra/newview/fsposingmotion.h b/indra/newview/fsposingmotion.h
index 52d6905947..c20f3b07c2 100644
--- a/indra/newview/fsposingmotion.h
+++ b/indra/newview/fsposingmotion.h
@@ -223,9 +223,9 @@ public:
void revertCollisionVolume();
///
- /// Sets all rotations to zero.
+ /// Sets the beginning and target rotations to the supplied rotation.
///
- void setRotationsToZero();
+ void setJointStartRotations(LLQuaternion quat);
///
/// Gets the pointer to the jointstate for the joint this represents.
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 51c909760c..c699d97c66 100644
--- a/indra/newview/skins/default/xui/en/floater_fs_poser.xml
+++ b/indra/newview/skins/default/xui/en/floater_fs_poser.xml
@@ -654,7 +654,7 @@ width="403">
background_visible="false"
layout="topleft"
visible="true"
- title="Posee"
+ title="Model"
mouse_opaque="false"
name="avatarSelection_panel"
top="0"
@@ -1137,7 +1137,7 @@ width="403">
name="button_RecaptureParts"
left_pad="1"
top_delta="0"
- tool_tip="If the selected body part(s) is/are OFF, this Recaptures what those body parts are doing right now"
+ tool_tip="If the selected body part(s) is/are OFF, this Recaptures what those body parts are doing right now. This button disables if you start from a T-Pose"
width="45" >