FIRE-35794: First pass at restoring pose state

Updated save version: now saves all the playing poses and their times, making 'diffs' much more useful.
Loading attempts to replay the pose at that time; making several tries if needed.
These poses restore the 'base' rotation state (position needs work).
master
Angeldark Raymaker 2025-09-14 20:47:28 +01:00
parent 3debcc1ca7
commit dddce2b568
12 changed files with 1091 additions and 65 deletions

View File

@ -167,6 +167,7 @@ set(viewer_SOURCE_FILES
fsparticipantlist.cpp
fspose.cpp
fsposeranimator.cpp
fsposestate.cpp
fsposingmotion.cpp
fsprimfeedauth.cpp
fsradar.cpp
@ -1007,6 +1008,7 @@ set(viewer_HEADER_FILES
fsparticipantlist.h
fspose.h
fsposeranimator.h
fsposestate.h
fsposingmotion.h
fsprimfeedauth.h
fsradar.h

View File

@ -45,6 +45,7 @@
#include "llvoavatarself.h"
#include "llinventoryfunctions.h"
#include "lltoolcomp.h"
#include "llloadingindicator.h"
namespace
{
@ -110,6 +111,8 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
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); });
mLoadPoseTimer = new FSLoadPoseTimer(boost::bind(&FSFloaterPoser::timedReload, this));
}
bool FSFloaterPoser::postBuild()
@ -253,7 +256,6 @@ void FSFloaterPoser::onOpen(const LLSD& key)
LLFloater::onOpen(key);
}
void FSFloaterPoser::onFocusReceived()
{
LLEditMenuHandler::gEditMenuHandler = this;
@ -302,6 +304,7 @@ void FSFloaterPoser::onClose(bool app_quitting)
}
disableVisualManipulators();
delete mLoadPoseTimer;
LLFloater::onClose(app_quitting);
}
@ -512,9 +515,11 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
{
bool savingDiff = !mPoserAnimator.allBaseRotationsAreZero(avatar);
LLSD record;
record["version"]["value"] = (S32)6;
record["version"]["value"] = (S32)7;
record["startFromTeePose"]["value"] = !savingDiff;
mPoserAnimator.savePosingState(avatar, &record);
LLVector3 rotation, position, scale, zeroVector;
bool baseRotationIsZero;
@ -523,12 +528,15 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
std::string bone_name = pj.jointName();
bool posingThisJoint = mPoserAnimator.isPosingAvatarJoint(avatar, pj);
bool jointRotLocked = mPoserAnimator.getRotationIsWorldLocked(avatar, pj);
bool jointRotMirrored = mPoserAnimator.getRotationIsMirrored(avatar, pj);
record[bone_name] = bone_name;
record[bone_name]["enabled"] = posingThisJoint;
record[bone_name] = bone_name;
record[bone_name]["enabled"] = posingThisJoint;
if (!posingThisJoint)
continue;
record[bone_name]["mirrored"] = jointRotMirrored;
if (!mPoserAnimator.tryGetJointSaveVectors(avatar, pj, &rotation, &position, &scale, &baseRotationIsZero))
continue;
@ -563,7 +571,6 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
return false;
}
return true;
}
@ -663,6 +670,8 @@ void FSFloaterPoser::onClickRecaptureSelectedBones()
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
mPoserAnimator.updatePosingState(avatar, selectedJoints);
for (auto item : selectedJoints)
{
bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item);
@ -892,14 +901,39 @@ void FSFloaterPoser::onPoseMenuAction(const LLSD& param)
else if (loadStyle == "selective_rot")
loadType = SELECTIVE_ROT;
mLoadPoseTimer->tryLoading(poseName, loadType);
}
void FSFloaterPoser::timedReload()
{
if (!mLoadPoseTimer)
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
loadPoseFromXml(avatar, poseName, loadType);
onJointTabSelect();
refreshJointScrollListMembers();
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
if (loadPoseFromXml(avatar, mLoadPoseTimer->getPosePath(), mLoadPoseTimer->getLoadMethod()))
{
setLoadingProgress(false);
onJointTabSelect();
refreshJointScrollListMembers();
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
}
else
{
setLoadingProgress(true);
}
}
void FSFloaterPoser::setLoadingProgress(bool started)
{
LLLoadingIndicator* mLoadingIndicator = findChild<LLLoadingIndicator>("progress_indicator");
if (!mLoadingIndicator)
return;
mLoadingIndicator->setVisible(started);
started ? mLoadingIndicator->start() : mLoadingIndicator->stop();
}
void FSFloaterPoser::onClickLoadLeftHandPose()
@ -1020,14 +1054,15 @@ bool FSFloaterPoser::poseFileStartsFromTeePose(const std::string& poseFileName)
return false;
}
void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod)
bool FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod)
{
bool loadSuccess = false;
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY);
if (!gDirUtilp->fileExists(pathname))
return;
return loadSuccess;
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
return loadSuccess;
std::string fullPath =
gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY, poseFileName + POSE_INTERNAL_FORMAT_FILE_EXT);
@ -1049,20 +1084,22 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
bool enabled;
bool setJointBaseRotationToZero;
bool worldLocked;
bool mirroredJoint;
S32 version = 0;
bool startFromZeroRot = true;
infile.open(fullPath);
if (!infile.is_open())
return;
return loadSuccess;
loadSuccess = true;
while (!infile.eof())
{
S32 lineCount = LLSDSerialize::fromXML(pose, infile);
if (lineCount == LLSDParser::PARSE_FAILURE)
{
LL_WARNS("Posing") << "Failed to parse file: " << poseFileName << LL_ENDL;
return;
return loadSuccess;
}
for (LLSD::map_const_iterator itr = pose.beginMap(); itr != pose.endMap(); ++itr)
@ -1127,13 +1164,22 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
worldLocked = control_map.has("worldLocked") ? control_map["worldLocked"].asBoolean() : false;
mPoserAnimator.setRotationIsWorldLocked(avatar, *poserJoint, worldLocked);
mirroredJoint = control_map.has("mirrored") ? control_map["mirrored"].asBoolean() : false;
mPoserAnimator.setRotationIsMirrored(avatar, *poserJoint, mirroredJoint);
}
if (version > 6)
loadSuccess = mPoserAnimator.loadPosingState(avatar, pose);
}
}
catch ( const std::exception & e )
{
loadSuccess = false;
LL_WARNS("Posing") << "Everything caught fire trying to load the pose: " << poseFileName << " exception: " << e.what() << LL_ENDL;
}
return loadSuccess;
}
void FSFloaterPoser::startPosingSelf()
@ -2720,3 +2766,31 @@ void FSFloaterPoser::onClickLockWorldRotBtn()
refreshTextHighlightingOnJointScrollLists();
}
FSLoadPoseTimer::FSLoadPoseTimer(FSLoadPoseTimer::callback_t callback) : LLEventTimer(0.5f), mCallback(callback)
{
}
bool FSLoadPoseTimer::tick()
{
if (!mAttemptLoading)
return false;
if (mLoadAttempts > 5)
return false;
if (mCallback.empty())
return false;
mCallback();
mLoadAttempts++;
return false;
}
void FSLoadPoseTimer::tryLoading(std::string filePath, E_LoadPoseMethods loadMethod)
{
mPoseFullPath = filePath;
mLoadType = loadMethod;
mLoadAttempts = 0;
mAttemptLoading = true;
}

View File

@ -39,6 +39,7 @@ class LLLineEditor;
class LLScrollListCtrl;
class LLSliderCtrl;
class LLTabContainer;
class FSLoadPoseTimer;
/// <summary>
/// Describes how to load a pose file.
@ -228,11 +229,12 @@ public:
bool savePoseToBvh(LLVOAvatar* avatar, const std::string& posePath);
void onClickBrowsePoseCache();
void onPoseMenuAction(const LLSD& param);
void loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod);
bool loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod);
bool poseFileStartsFromTeePose(const std::string& poseFileName);
void setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName();
void setUiSelectedAvatarSaveFileName(const std::string& saveFileName);
bool confirmFileOverwrite(std::string fileName);
void timedReload();
void setLoadingProgress(bool started);
void startPosingSelf();
void stopPosingAllAvatars();
// visual manipulators control
@ -502,6 +504,8 @@ public:
LLLineEditor* mPoseSaveNameEditor{ nullptr };
FSLoadPoseTimer* mLoadPoseTimer;
LLPanel* mJointsParentPnl{ nullptr };
LLPanel* mTrackballPnl{ nullptr };
LLPanel* mPositionRotationPnl{ nullptr };
@ -530,4 +534,24 @@ public:
LLUICtrl* mScaleZSpnr{ nullptr };
};
class FSLoadPoseTimer : public LLEventTimer
{
public:
typedef boost::function<void()> callback_t;
FSLoadPoseTimer(callback_t callback);
/*virtual*/ bool tick();
void tryLoading(std::string filePath, E_LoadPoseMethods loadMethod);
std::string getPosePath() { return mPoseFullPath; };
E_LoadPoseMethods getLoadMethod() { return mLoadType; };
private:
callback_t mCallback;
bool mAttemptLoading = false;
E_LoadPoseMethods mLoadType = ROT_POS_AND_SCALES;
std::string mPoseFullPath;
int mLoadAttempts = 0;
};
#endif

View File

@ -55,6 +55,7 @@ void FSJointPose::setPublicPosition(const LLVector3& pos)
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mPosition.set(pos);
mCurrentState.mLastChangeWasRotational = false;
}
void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
@ -65,17 +66,22 @@ void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
zeroBaseRotation(true);
mCurrentState.mRotation.set(rot);
mCurrentState.mLastChangeWasRotational = true;
}
void FSJointPose::setPublicScale(const LLVector3& scale)
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mScale.set(scale);
mCurrentState.mLastChangeWasRotational = false;
}
void FSJointPose::undoLastChange()
bool FSJointPose::undoLastChange()
{
mCurrentState = undoLastStateChange(FSJointState(mCurrentState));
bool changeType = mCurrentState.mLastChangeWasRotational;
mCurrentState = undoLastStateChange(FSJointState(mCurrentState));
return changeType;
}
void FSJointPose::redoLastChange()
@ -87,6 +93,7 @@ void FSJointPose::resetJoint()
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.resetJoint();
mCurrentState.mLastChangeWasRotational = true;
}
void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo)
@ -153,6 +160,7 @@ void FSJointPose::recaptureJoint()
addStateToUndo(FSJointState(mCurrentState));
mCurrentState = FSJointState(joint);
mCurrentState.mLastChangeWasRotational = true;
}
LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase)
@ -162,9 +170,30 @@ LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase)
return LLQuaternion::DEFAULT;
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mLastChangeWasRotational = true;
return mCurrentState.updateFromJoint(joint, zeroBase);
}
void FSJointPose::setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority)
{
mCurrentState.resetBaseRotation(rotation, priority);
}
void FSJointPose::setBasePosition(LLVector3 position, LLJoint::JointPriority priority)
{
mCurrentState.resetBasePosition(position, priority);
}
void FSJointPose::setBaseScale(LLVector3 scale, LLJoint::JointPriority priority)
{
mCurrentState.resetBaseScale(scale, priority);
}
void FSJointPose::setJointPriority(LLJoint::JointPriority priority)
{
mCurrentState.setPriority(priority);
}
void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
{
if (!oppositeJoint)
@ -177,6 +206,18 @@ void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
oppositeJoint->mCurrentState.cloneRotationFrom(tempState);
}
void FSJointPose::swapBaseRotationWith(FSJointPose* oppositeJoint)
{
if (!oppositeJoint)
return;
if (mIsCollisionVolume)
return;
auto tempState = FSJointState(mCurrentState);
mCurrentState.cloneBaseRotationFrom(oppositeJoint->mCurrentState);
oppositeJoint->mCurrentState.cloneBaseRotationFrom(tempState);
}
void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint)
{
if (!fromJoint)
@ -184,6 +225,7 @@ void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint)
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.cloneRotationFrom(fromJoint->mCurrentState);
mCurrentState.mLastChangeWasRotational = true;
}
void FSJointPose::mirrorRotationFrom(FSJointPose* fromJoint)
@ -208,6 +250,14 @@ void FSJointPose::reflectRotation()
mCurrentState.reflectRotation();
}
void FSJointPose::reflectBaseRotation()
{
if (mIsCollisionVolume)
return;
mCurrentState.reflectBaseRotation();
}
void FSJointPose::zeroBaseRotation(bool lockInBvh)
{
if (mIsCollisionVolume)
@ -244,20 +294,24 @@ bool FSJointPose::userHasSetBaseRotationToZero() const
bool FSJointPose::getWorldRotationLockState() const
{
if (mIsCollisionVolume)
return false;
return mCurrentState.mRotationIsWorldLocked;
}
void FSJointPose::setWorldRotationLockState(bool newState)
{
if (mIsCollisionVolume)
return;
mCurrentState.mRotationIsWorldLocked = newState;
}
bool FSJointPose::getRotationMirrorState() const
{
return mCurrentState.mJointRotationIsMirrored;
}
void FSJointPose::setRotationMirrorState(bool newState)
{
mCurrentState.mJointRotationIsMirrored = newState;
}
bool FSJointPose::canPerformUndo() const
{
switch (mLastSetJointStates.size())

View File

@ -70,7 +70,8 @@ class FSJointPose
/// <summary>
/// Undoes the last position set, if any.
/// </summary>
void undoLastChange();
/// <returns>true if the change we un-did was rotational.</returns>
bool undoLastChange();
/// <summary>
/// Undoes the last position set, if any.
@ -104,6 +105,11 @@ class FSJointPose
/// </summary>
void reflectRotation();
/// <summary>
/// Reflects the base rotation of the represented joint left-right.
/// </summary>
void reflectBaseRotation();
/// <summary>
/// Sets the private rotation of the represented joint to zero.
/// </summary>
@ -143,6 +149,11 @@ class FSJointPose
/// </summary>
void swapRotationWith(FSJointPose* oppositeJoint);
/// <summary>
/// Exchanges the base rotations between two joints.
/// </summary>
void swapBaseRotationWith(FSJointPose* oppositeJoint);
/// <summary>
/// Clones the rotation to this from the supplied joint.
/// </summary>
@ -165,6 +176,33 @@ class FSJointPose
/// <returns>The rotation of the public difference between before and after recapture.</returns>
LLQuaternion recaptureJointAsDelta(bool zeroBase);
/// <summary>
/// Sets the base rotation to the supplied rotation if the supplied priority is appropriate.
/// </summary>
/// <param name="rotation">The base rotation to set; zero is ignored.</param>
/// <param name="priority">The priority of the base rotation; only priority equal or higher than any prior sets have any effect.</param>
void setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority);
/// <summary>
/// Sets the base position to the supplied position if the supplied priority is appropriate.
/// </summary>
/// <param name="position">The base position to set; zero is ignored.</param>
/// <param name="priority">The priority of the base rotation; only priority equal or higher than any prior sets have any effect.</param>
void setBasePosition(LLVector3 position, LLJoint::JointPriority priority);
/// <summary>
/// Sets the base scale to the supplied scale if the supplied priority is appropriate.
/// </summary>
/// <param name="scale">The base scale to set; zero is ignored.</param>
/// <param name="priority">The priority of the base rotation; only priority equal or higher than any prior sets have any effect.</param>
void setBaseScale(LLVector3 scale, LLJoint::JointPriority priority);
/// <summary>
/// Sets the priority of the bone to the supplied value.
/// </summary>
/// <param name="priority">The new priority of the base rotation.</param>
void setJointPriority(LLJoint::JointPriority priority);
/// <summary>
/// Clears the undo/redo deque.
/// </summary>
@ -188,6 +226,18 @@ class FSJointPose
/// <param name="newState">The new state for the world-rotation lock.</param>
void setWorldRotationLockState(bool newState);
/// <summary>
/// Gets whether the rotation of a joint has been mirrored.
/// </summary>
/// <returns>True if the joint has been mirrored, otherwise false.</returns>
bool getRotationMirrorState() const;
/// <summary>
/// Sets whether the rotation of a joint has been mirrored.
/// </summary>
/// <param name="newState">The new state for the mirror.</param>
void setRotationMirrorState(bool newState);
/// <summary>
/// Reverts the position/rotation/scale to their values when the animation begun.
/// This treatment is required for certain joints, particularly Collision Volumes and those bones not commonly animated by an AO.
@ -212,41 +262,64 @@ class FSJointPose
mBaseRotation.set(joint->getRotation());
mBasePosition.set(joint->getPosition());
mBaseScale.set(joint->getScale());
mBasePositionFromAnimation.setZero();
mBaseScaleFromAnimation.setZero();
}
FSJointState() = default;
LLQuaternion getTargetRotation() const { return mRotation * mBaseRotation; }
LLVector3 getTargetPosition() const { return mPosition + mBasePosition; }
LLVector3 getTargetScale() const { return mScale + mBaseScale; }
LLVector3 getTargetPosition() const { return mPosition + mBasePosition + mBasePositionFromAnimation; }
LLVector3 getTargetScale() const { return mScale + mBaseScale + mBaseScaleFromAnimation; }
void reflectRotation()
{
mBaseRotation.mQ[VX] *= -1;
mBaseRotation.mQ[VZ] *= -1;
reflectBaseRotation();
mRotation.mQ[VX] *= -1;
mRotation.mQ[VZ] *= -1;
mJointRotationIsMirrored = !mJointRotationIsMirrored;
}
void reflectBaseRotation()
{
mBaseRotation.mQ[VX] *= -1;
mBaseRotation.mQ[VZ] *= -1;
}
void cloneRotationFrom(FSJointState otherState)
{
mBaseRotation.set(otherState.mBaseRotation);
cloneBaseRotationFrom(otherState);
mRotation.set(otherState.mRotation);
mUserSpecifiedBaseZero = otherState.mUserSpecifiedBaseZero;
}
void cloneBaseRotationFrom(FSJointState otherState)
{
mBaseRotation.set(otherState.mBaseRotation);
}
bool baseRotationIsZero() const { return mBaseRotation == LLQuaternion::DEFAULT; }
void resetJoint()
{
mUserSpecifiedBaseZero = false;
mRotationIsWorldLocked = false;
mUserSpecifiedBaseZero = false;
mRotationIsWorldLocked = false;
mJointRotationIsMirrored = false;
mLastChangeWasRotational = true;
mBaseRotation.set(mStartingRotation);
mRotation.set(LLQuaternion::DEFAULT);
mPosition.setZero();
mScale.setZero();
mBasePositionFromAnimation.setZero();
mBaseScaleFromAnimation.setZero();
}
void zeroBaseRotation() { mBaseRotation = LLQuaternion::DEFAULT; }
void zeroBaseRotation()
{
mBasePriority = LLJoint::LOW_PRIORITY;
mBaseRotation = LLQuaternion::DEFAULT;
mJointRotationIsMirrored = false;
}
void revertJointToBase(LLJoint* joint) const
{
@ -275,12 +348,50 @@ class FSJointPose
}
mRotation.set(newPublicRot);
mPosition.set(joint->getPosition() - mBasePosition);
mScale.set(joint->getScale() - mBaseScale);
mPosition.set(joint->getPosition() - mBasePosition - mBasePositionFromAnimation);
mScale.set(joint->getScale() - mBaseScale - mBaseScaleFromAnimation);
return newPublicRot *= ~initalPublicRot;
}
void resetBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority)
{
if (priority < mBasePriority)
return;
if (rotation == LLQuaternion::DEFAULT)
return;
LL_WARNS("Posing") << "Loaded rot: " << rotation << " at priority " << priority << LL_ENDL;
mBasePriority = priority;
mBaseRotation.set(rotation);
}
void resetBasePosition(LLVector3 position, LLJoint::JointPriority priority)
{
if (priority < mBasePriority)
return;
LL_WARNS("Posing") << "Loaded pos: " << position << " at priority " << priority << LL_ENDL;
mBasePriority = priority;
mBasePositionFromAnimation.set(position);
}
void resetBaseScale(LLVector3 scale, LLJoint::JointPriority priority)
{
if (priority < mBasePriority)
return;
if (scale.isExactlyZero())
return;
LL_WARNS("Posing") << "Loaded pos: " << scale << " at priority " << priority << LL_ENDL;
mBasePriority = priority;
mBaseScaleFromAnimation.set(scale);
}
void setPriority(LLJoint::JointPriority priority) { mBasePriority = priority; }
private:
FSJointState(FSJointState* state)
{
@ -288,12 +399,18 @@ class FSJointPose
mBaseRotation.set(state->mBaseRotation);
mBasePosition.set(state->mBasePosition);
mBaseScale.set(state->mBaseScale);
mBasePositionFromAnimation.set(state->mBasePositionFromAnimation);
mBaseScaleFromAnimation.set(state->mBaseScaleFromAnimation);
mRotation.set(state->mRotation);
mPosition.set(state->mPosition);
mScale.set(state->mScale);
mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero;
mRotationIsWorldLocked = state->mRotationIsWorldLocked;
mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero;
mRotationIsWorldLocked = state->mRotationIsWorldLocked;
mBasePriority = state->mBasePriority;
mJointRotationIsMirrored = state->mJointRotationIsMirrored;
mLastChangeWasRotational = state->mLastChangeWasRotational;
}
public:
@ -301,6 +418,15 @@ class FSJointPose
LLVector3 mPosition;
LLVector3 mScale;
bool mRotationIsWorldLocked = false;
bool mLastChangeWasRotational = false;
/// <summary>
/// Whether the joint has been mirrored.
/// </summary>
/// <remarks>
/// Used when loading a diff; indicating that the base-rotations, once restored, need to be swapped.
/// </remarks>
bool mJointRotationIsMirrored = false;
/// <summary>
/// A value indicating whether the user has explicitly set the base rotation to zero.
@ -317,7 +443,10 @@ class FSJointPose
LLQuaternion mStartingRotation;
LLQuaternion mBaseRotation;
LLVector3 mBasePosition;
LLVector3 mBasePositionFromAnimation;
LLVector3 mBaseScale;
LLVector3 mBaseScaleFromAnimation;
LLJoint::JointPriority mBasePriority = LLJoint::LOW_PRIORITY;
};
private:

View File

@ -94,8 +94,8 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!jointPose)
return;
jointPose->undoLastChange();
undoOrRedoWorldLockedDescendants(joint, posingMotion, false);
if (jointPose->undoLastChange())
undoOrRedoWorldLockedDescendants(joint, posingMotion, false);
if (style == NONE || style == DELTAMODE)
return;
@ -104,7 +104,8 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!oppositeJointPose)
return;
oppositeJointPose->undoLastChange();
if (!oppositeJointPose->undoLastChange())
return;
auto oppositePoserJoint = getPoserJointByName(joint.mirrorJointName());
if (oppositePoserJoint)
@ -271,6 +272,38 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j
}
}
bool FSPoserAnimator::getRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
if (!isAvatarSafeToUse(avatar))
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return false;
return jointPose->getRotationMirrorState();
}
void FSPoserAnimator::setRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState)
{
if (!isAvatarSafeToUse(avatar))
return;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return;
jointPose->setRotationMirrorState(newState);
}
bool FSPoserAnimator::getRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
if (!isAvatarSafeToUse(avatar))
@ -923,6 +956,56 @@ void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joi
jointPose->setPublicScale(scale);
}
bool FSPoserAnimator::loadPosingState(LLVOAvatar* avatar, LLSD pose)
{
if (!isAvatarSafeToUse(avatar))
return false;
mPosingState.purgeMotionStates(avatar);
mPosingState.restoreMotionStates(avatar, pose);
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
// TODO: do I need to zero all bases first to reset latent rotations?
bool loadSuccess = mPosingState.applyMotionStatesToPosingMotion(avatar, posingMotion);
if (loadSuccess)
applyJointMirrorToBaseRotations(posingMotion);
return loadSuccess;
}
void FSPoserAnimator::applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion)
{
for (size_t index = 0; index != PoserJoints.size(); ++index)
{
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(PoserJoints[index].jointName());
if (!jointPose)
continue;
if (!jointPose->getRotationMirrorState())
continue;
if (PoserJoints[index].dontFlipOnMirror()) // we only flip one side.
continue;
jointPose->reflectBaseRotation();
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(PoserJoints[index].mirrorJointName());
if (!oppositeJointPose)
continue;
oppositeJointPose->reflectBaseRotation();
jointPose->swapBaseRotationWith(oppositeJointPose);
}
}
void FSPoserAnimator::savePosingState(LLVOAvatar* avatar, LLSD* saveRecord)
{
mPosingState.writeMotionStates(avatar, saveRecord);
}
const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) const
{
for (size_t index = 0; index != PoserJoints.size(); ++index)
@ -948,6 +1031,8 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar)
if (avatar->isSelf())
gAgent.stopFidget();
mPosingState.captureMotionStates(avatar);
avatar->startDefaultMotions();
avatar->startMotion(posingMotion->motionId());
@ -957,6 +1042,22 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar)
return false;
}
void FSPoserAnimator::updatePosingState(LLVOAvatar* avatar, std::vector<FSPoserAnimator::FSPoserJoint*> jointsRecaptured)
{
if (!avatar)
return;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return;
std::string jointNamesRecaptured;
for (auto item : jointsRecaptured)
jointNamesRecaptured += item->jointName();
mPosingState.updateMotionStates(avatar, posingMotion, jointNamesRecaptured);
}
void FSPoserAnimator::stopPosingAvatar(LLVOAvatar *avatar)
{
if (!avatar || avatar->isDead())
@ -966,6 +1067,7 @@ void FSPoserAnimator::stopPosingAvatar(LLVOAvatar *avatar)
if (!posingMotion)
return;
mPosingState.purgeMotionStates(avatar);
avatar->stopMotion(posingMotion->motionId());
}
@ -1006,7 +1108,6 @@ FSPosingMotion* FSPoserAnimator::findOrCreatePosingMotion(LLVOAvatar* avatar)
sAvatarIdToRegisteredAnimationId[avatar->getID()] = animationAssetId;
return dynamic_cast<FSPosingMotion*>(avatar->createMotion(animationAssetId));
}
bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const
@ -1118,7 +1219,11 @@ void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& join
if (jointPose->getWorldRotationLockState())
{
redo ? jointPose->redoLastChange() : jointPose->undoLastChange();
if (redo)
jointPose->redoLastChange();
else
jointPose->undoLastChange();
return;
}

View File

@ -28,6 +28,7 @@
#define LL_FSPoserAnimator_H
#include "fsposingmotion.h"
#include "fsposestate.h"
#include "llvoavatar.h"
/// <summary>
@ -197,27 +198,27 @@ public:
/// </remarks>
const std::vector<FSPoserJoint> PoserJoints{
// head, torso, legs
{ "mHead", "", BODY, { "mEyeLeft", "mEyeRight", "mFaceRoot", "mSkull" }, "0.000 0.076 0.000" },
{ "mNeck", "", BODY, { "mHead" }, "0.000 0.251 -0.010" },
{ "mPelvis", "", WHOLEAVATAR, { "mSpine1", "mHipLeft", "mHipRight", "mTail1", "mGroin", "mHindLimbsRoot" }, "0.000000 0.000000 0.000000" },
{ "mChest", "", BODY, { "mNeck", "mCollarLeft", "mCollarRight", "mWingsRoot" }, "0.000 0.205 -0.015" },
{ "mTorso", "", BODY, { "mSpine3" }, "0.000 0.084 0.000" },
{ "mCollarLeft", "mCollarRight", BODY, { "mShoulderLeft" }, "0.085 0.165 -0.021" },
{ "mShoulderLeft", "mShoulderRight", BODY, { "mElbowLeft" }, "0.079 0.000 0.000" },
{ "mElbowLeft", "mElbowRight", BODY, { "mWristLeft" }, "0.248 0.000 0.000" },
{ "mWristLeft", "mWristRight", BODY, { "mHandThumb1Left", "mHandIndex1Left", "mHandMiddle1Left", "mHandRing1Left", "mHandPinky1Left" }, "0.205 0.000 0.000" },
{ "mCollarRight", "mCollarLeft", BODY, { "mShoulderRight" }, "-0.085 0.165 -0.021", "", true },
{ "mShoulderRight", "mShoulderLeft", BODY, { "mElbowRight" }, "-0.079 0.000 0.000", "", true },
{ "mElbowRight", "mElbowLeft", BODY, { "mWristRight" }, "-0.248 0.000 0.000", "", true },
{ "mWristRight", "mWristLeft", BODY, { "mHandThumb1Right", "mHandIndex1Right", "mHandMiddle1Right", "mHandRing1Right", "mHandPinky1Right" }, "-0.205 0.000 0.000", "", true },
{ "mHipLeft", "mHipRight", BODY, { "mKneeLeft" }, "0.127 -0.041 0.034" },
{ "mKneeLeft", "mKneeRight", BODY, { "mAnkleLeft" }, "-0.046 -0.491 -0.001" },
{ "mAnkleLeft", "mAnkleRight", BODY, { "mFootLeft" }, "0.001 -0.468 -0.029" },
{ "mHead", "", BODY, { "mEyeLeft", "mEyeRight", "mFaceRoot", "mSkull", "HEAD" }, "0.000 0.076 0.000" },
{ "mNeck", "", BODY, { "mHead", "NECK" }, "0.000 0.251 -0.010" },
{ "mPelvis", "", WHOLEAVATAR, { "mSpine1", "mHipLeft", "mHipRight", "mTail1", "mGroin", "mHindLimbsRoot", "PELVIS", "BUTT" }, "0.000000 0.000000 0.000000" },
{ "mChest", "", BODY, { "mNeck", "mCollarLeft", "mCollarRight", "mWingsRoot", "CHEST", "LEFT_PEC", "RIGHT_PEC", "UPPER_BACK" }, "0.000 0.205 -0.015" },
{ "mTorso", "", BODY, { "mSpine3", "BELLY", "LEFT_HANDLE", "RIGHT_HANDLE", "LOWER_BACK" }, "0.000 0.084 0.000" },
{ "mCollarLeft", "mCollarRight", BODY, { "mShoulderLeft", "L_CLAVICLE" }, "0.085 0.165 -0.021" },
{ "mShoulderLeft", "mShoulderRight", BODY, { "mElbowLeft", "L_UPPER_ARM" }, "0.079 0.000 0.000" },
{ "mElbowLeft", "mElbowRight", BODY, { "mWristLeft", "L_LOWER_ARM" }, "0.248 0.000 0.000" },
{ "mWristLeft", "mWristRight", BODY, { "mHandThumb1Left", "mHandIndex1Left", "mHandMiddle1Left", "mHandRing1Left", "mHandPinky1Left", "L_HAND" }, "0.205 0.000 0.000" },
{ "mCollarRight", "mCollarLeft", BODY, { "mShoulderRight", "R_CLAVICLE" }, "-0.085 0.165 -0.021", "", true },
{ "mShoulderRight", "mShoulderLeft", BODY, { "mElbowRight", "R_UPPER_ARM" }, "-0.079 0.000 0.000", "", true },
{ "mElbowRight", "mElbowLeft", BODY, { "mWristRight", "R_LOWER_ARM" }, "-0.248 0.000 0.000", "", true },
{ "mWristRight", "mWristLeft", BODY, { "mHandThumb1Right", "mHandIndex1Right", "mHandMiddle1Right", "mHandRing1Right", "mHandPinky1Right", "R_HAND" }, "-0.205 0.000 0.000", "", true },
{ "mHipLeft", "mHipRight", BODY, { "mKneeLeft", "L_UPPER_LEG" }, "0.127 -0.041 0.034" },
{ "mKneeLeft", "mKneeRight", BODY, { "mAnkleLeft", "L_LOWER_LEG" }, "-0.046 -0.491 -0.001" },
{ "mAnkleLeft", "mAnkleRight", BODY, { "mFootLeft", "L_FOOT" }, "0.001 -0.468 -0.029" },
{ "mFootLeft", "mFootRight", BODY, { "mToeLeft" }, "0.000 -0.061 0.112" },
{ "mToeLeft", "mToeRight", BODY, {}, "0.000 0.000 0.109", "0.000 0.020 0.000" },
{ "mHipRight", "mHipLeft", BODY, { "mKneeRight" }, "-0.129 -0.041 0.034", "", true },
{ "mKneeRight", "mKneeLeft", BODY, { "mAnkleRight" }, "0.049 -0.491 -0.001", "", true },
{ "mAnkleRight", "mAnkleLeft", BODY, { "mFootRight" }, "0.000 -0.468 -0.029", "", true },
{ "mHipRight", "mHipLeft", BODY, { "mKneeRight", "R_UPPER_LEG" }, "-0.129 -0.041 0.034", "", true },
{ "mKneeRight", "mKneeLeft", BODY, { "mAnkleRight", "R_LOWER_LEG" }, "0.049 -0.491 -0.001", "", true },
{ "mAnkleRight", "mAnkleLeft", BODY, { "mFootRight", "R_FOOT" }, "0.000 -0.468 -0.029", "", true },
{ "mFootRight", "mFootLeft", BODY, { "mToeRight" }, "0.000 -0.061 0.112", "", true },
{ "mToeRight", "mToeLeft", BODY, {}, "0.000 0.000 0.109", "0.000 0.020 0.000", true },
@ -617,6 +618,22 @@ public:
/// <param name="newState">The lock state to apply.</param>
void setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState);
/// <summary>
/// Gets whether the supplied joint for the supplied avatar has been mirrored.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>True if the joint is maintaining a fixed-rotation in world, otherwise false.</returns>
bool getRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
/// <summary>
/// Sets the mirrored status for supplied joint for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <param name="newState">The mirror state to apply.</param>
void setRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState);
/// <summary>
/// Determines if the kind of save to perform should be a 'delta' save, or a complete save.
/// </summary>
@ -690,6 +707,42 @@ public:
/// </remarks>
void loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale);
/// <summary>
/// Loads the posing state (base rotations) to the supplied avatars posing-motion, from the supplied record.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be loaded.</param>
/// <param name="pose">The record to read the posing state from.</param>
/// <returns>True if the pose loaded successfully, otherwise false.</returns>
/// <remarks>
/// When a save embeds animations that need to be restored at a certain time,
/// it can take several frames for the animation to be loaded and ready.
/// It may therefore be necessary to attempt this several times.
/// </remarks>
bool loadPosingState(LLVOAvatar* avatar, LLSD pose);
/// <summary>
/// Adds the posing state for the supplied avatar to the supplied record.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be written.</param>
/// <param name="saveRecord">The record to write the posing state to.</param>
void savePosingState(LLVOAvatar* avatar, LLSD* saveRecord);
/// <summary>
/// Purges and recaptures the pose state for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar whose pose state is to be recapture.</param>
/// <param name="jointsRecaptured">The joints which were recaptured.</param>
void updatePosingState(LLVOAvatar* avatar, std::vector<FSPoserAnimator::FSPoserJoint*> jointsRecaptured);
/// <summary>
/// Traverses the joints and applies reversals to the base rotations if needed.
/// </summary>
/// <param name="posingMotion">The posing motion whose pose states require updating.</param>
/// <remarks>
/// Required after restoring a diff. The base rotations will be in their original arrangment.
/// </remarks>
void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion);
private:
/// <summary>
/// Translates a rotation vector from the UI to a Quaternion for the bone.
@ -788,6 +841,8 @@ public:
/// Is static, so the animationId is not lost between sessions (such as when the UI floater is closed and reopened).
/// </summary>
static std::map<LLUUID, LLAssetID> sAvatarIdToRegisteredAnimationId;
FSPoseState mPosingState;
};
#endif // LL_FSPoserAnimator_H

View File

@ -0,0 +1,249 @@
#include "fsposestate.h"
#include "llinventorymodel.h" // gInventory
std::map<LLUUID, std::vector<FSPoseState::fsMotionState>> FSPoseState::sMotionStates;
std::map<LLUUID, int> FSPoseState::sCaptureOrder;
void FSPoseState::captureMotionStates(LLVOAvatar* avatar)
{
if (!avatar)
return;
sCaptureOrder[avatar->getID()] = 0;
for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); ++anim_it)
{
LLKeyframeMotion* motion = dynamic_cast<LLKeyframeMotion*>(avatar->findMotion(anim_it->first));
if (!motion)
continue;
fsMotionState newState;
newState.avatarId = avatar->getID();
newState.motionId = anim_it->first;
newState.lastUpdateTime = motion->getLastUpdateTime();
newState.captureOrder = 0;
sMotionStates[avatar->getID()].push_back(newState);
}
}
void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured)
{
if (!avatar || !posingMotion)
return;
sCaptureOrder[avatar->getID()]++;
// if an animation for avatar is a subset of jointNamesRecaptured, delete it
// this happens on second/subsequent recaptures; the first recapture is no longer needed
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end();)
{
bool avatarMatches = (*it).avatarId == avatar->getID();
std::string joints = (*it).jointNamesAnimated;
bool recaptureMatches = !joints.empty() && !jointNamesRecaptured.empty() && jointNamesRecaptured.find(joints) != std::string::npos;
if (avatarMatches && recaptureMatches)
it = sMotionStates[avatar->getID()].erase(it);
else
it++;
}
for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); anim_it++)
{
LLKeyframeMotion* motion = dynamic_cast<LLKeyframeMotion*>(avatar->findMotion(anim_it->first));
if (!motion)
continue;
if (!posingMotion->otherMotionAnimatesJoints(motion, jointNamesRecaptured))
continue;
bool foundMatch = false;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
bool avatarMatches = (*it).avatarId == avatar->getID();
bool motionIdMatches = (*it).motionId == anim_it->first;
bool updateTimesMatch = (*it).lastUpdateTime == motion->getLastUpdateTime(); // consider when recapturing the same animation at different times for a subset of bones
foundMatch = avatarMatches && motionIdMatches && updateTimesMatch;
if (foundMatch)
break;
}
if (foundMatch)
continue;
fsMotionState newState;
newState.avatarId = avatar->getID();
newState.motionId = anim_it->first;
newState.lastUpdateTime = motion->getLastUpdateTime();
newState.jointNamesAnimated = jointNamesRecaptured;
newState.captureOrder = sCaptureOrder[avatar->getID()];
sMotionStates[avatar->getID()].push_back(newState);
}
}
void FSPoseState::purgeMotionStates(LLVOAvatar* avatar)
{
if (!avatar)
return;
std::vector<fsMotionState>::iterator it;
for (it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end();)
{
if ((*it).avatarId == avatar->getID())
it = sMotionStates[avatar->getID()].erase(it);
else
it++;
}
}
void FSPoseState::writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord)
{
if (!avatar)
return;
int animNumber = 0;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); ++it)
{
if (it->avatarId != avatar->getID())
continue;
std::string uniqueAnimId = "poseState" + std::to_string(animNumber++);
(*saveRecord)[uniqueAnimId]["animationId"] = it->motionId.asString();
(*saveRecord)[uniqueAnimId]["lastUpdateTime"] = it->lastUpdateTime;
(*saveRecord)[uniqueAnimId]["jointNamesAnimated"] = it->jointNamesAnimated;
(*saveRecord)[uniqueAnimId]["captureOrder"] = it->captureOrder;
(*saveRecord)[uniqueAnimId]["playOrder"] = animNumber;
}
}
void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, LLSD pose)
{
if (!avatar)
return;
sCaptureOrder[avatar->getID()] = 0;
for (auto itr = pose.beginMap(); itr != pose.endMap(); ++itr)
{
std::string const& name = itr->first;
LLSD const& control_map = itr->second;
if (!name.starts_with("poseState"))
continue;
fsMotionState newState;
newState.avatarId = avatar->getID();
if (control_map.has("animationId"))
{
std::string const name = control_map["animationId"].asString();
LLUUID animId;
if (LLUUID::parseUUID(name, &animId))
newState.motionId = animId;
}
if (control_map.has("lastUpdateTime"))
newState.lastUpdateTime = (F32)control_map["lastUpdateTime"].asReal();
if (control_map.has("jointNamesAnimated"))
newState.jointNamesAnimated = control_map["jointNamesAnimated"].asString();
if (control_map.has("captureOrder"))
newState.captureOrder = control_map["captureOrder"].asInteger();
if (newState.captureOrder > sCaptureOrder[avatar->getID()])
sCaptureOrder[avatar->getID()] = newState.captureOrder;
sMotionStates[avatar->getID()].push_back(newState);
}
}
bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion)
{
if (!avatar || !posingMotion)
return false;
bool allMotionsApplied = true;
std::sort(sMotionStates[avatar->getID()].begin(), sMotionStates[avatar->getID()].end(), compareByCaptureOrder());
int lastCaptureOrder = 0;
bool needPriorityReset = false;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
if (it->avatarId != avatar->getID())
continue;
needPriorityReset = it->captureOrder > lastCaptureOrder;
if (it->motionApplied)
continue;
if (!avatarCanUsePose(avatar, it->motionId))
{
it->motionApplied = true;
continue;
}
LLKeyframeMotion* kfm = dynamic_cast<LLKeyframeMotion*>(avatar->findMotion(it->motionId));
if (kfm)
{
if (needPriorityReset)
{
lastCaptureOrder = it->captureOrder;
LL_WARNS("Posing") << "Resetting priority at cap order: " << lastCaptureOrder << LL_ENDL;
resetPriorityForCaptureOrder(avatar, posingMotion, lastCaptureOrder);
}
it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNamesAnimated);
}
else
{
avatar->startMotion(it->motionId); // only start if not a kfm; then wait until it casts as a kfm
avatar->stopMotion(it->motionId); // only stop if we have used it and we started it
}
allMotionsApplied &= it->motionApplied;
}
return allMotionsApplied;
}
void FSPoseState::resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder)
{
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
if (it->jointNamesAnimated.empty())
continue;
if (it->avatarId != avatar->getID())
continue;
if (it->motionApplied)
continue;
if (it->captureOrder != captureOrder)
continue;
LL_WARNS("Posing") << "Resetting priority for: " << it->jointNamesAnimated << LL_ENDL;
posingMotion->resetBonePriority(it->jointNamesAnimated);
}
}
bool FSPoseState::avatarCanUsePose(LLVOAvatar* avatar, LLUUID motionId)
{
if (!avatar)
return true;
if (!motionId.notNull())
return true;
if (avatar != gAgentAvatarp)
return true;
LLInventoryItem* item = gInventory.getItem(motionId);
if (!item)
return true;
return item->getPermissions().getOwner() == avatar->getID();
}

158
indra/newview/fsposestate.h Normal file
View File

@ -0,0 +1,158 @@
/**
* @file fsposestate.h
* @brief a means to save and restore the instantaneous state of animations posing an avatar.
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (c) 2025 Angeldark Raymaker @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_FSPoseState_H
#define LL_FSPoseState_H
#include "llvoavatar.h"
#include "fsposingmotion.h"
class FSPoseState
{
public:
FSPoseState() = default;
virtual ~FSPoseState() = default;
public:
/// <summary>
/// Captures the current animations posing the supplied avatar and how long they have been playing.
/// </summary>
/// <param name="avatar">The avatar whose animations are to be captured.</param>
/// <remarks>
/// Only animations owned by the supplied avatar are documented.
/// </remarks>
void captureMotionStates(LLVOAvatar* avatar);
/// <summary>
/// Updates the stored list of animations posing the avatar.
/// </summary>
/// <param name="avatar">The avatar whose animations are to be captured.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="jointNamesRecaptured">The names of the joints being recaptured.</param>
void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured);
/// <summary>
/// Removes all current animation states for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar whose animations are to be purged.</param>
void purgeMotionStates(LLVOAvatar* avatar);
/// <summary>
/// Writes any documented poses for the supplied avatar to the supplied stream.
/// </summary>
/// <param name="avatar">The avatar whose animations may have been captured.</param>
/// <param name="saveRecord">The record to add to.</param>
void writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord);
/// <summary>
/// Restores pose state(s) from the supplied record.
/// </summary>
/// <param name="avatar">The avatar whose animations may have been captured.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="pose">The record to read from.</param>
void restoreMotionStates(LLVOAvatar* avatar, LLSD pose);
/// <summary>
/// Applies the motion states for the supplied avatar to the supplied motion.
/// </summary>
/// <param name="avatar">The avatar to apply the motion state(s) to.</param>
/// <param name="posingMotion">The posing motion to apply the state(s) to.</param>
/// <returns>True if all the motion states for the supplied avatar have been applied, otherwise false.</returns>
/// <remarks>
/// In some ways this is like an AO: loading LLKeyframeMotions.
/// Once loaded, the LLKeyframeMotion is put at time fsMotionState.lastUpdateTime.
/// The joint-rotations for that LLKeyframeMotion are then restored to the base.
/// This examines sMotionStates for any avatarId matches; such as after a restoreMotionStates(...).
/// This could result in loading assets, thus a particular member of sMotionStates may take several attempts to load.
/// Motion(s) that the avatar does not have permissions for are not considered in the return boolean.
/// </remarks>
bool applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion);
void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder);
private:
/// <summary>
/// A class documenting the state of an animation for an avatar.
/// </summary>
class fsMotionState
{
public:
/// <summary>
/// The avatar ID this record is associated with.
/// </summary>
LLUUID avatarId;
/// <summary>
/// The motion ID recorded animating the avatar ID.
/// </summary>
LLAssetID motionId;
/// <summary>
/// The play-time the motionId had progressed until the motion was captured.
/// </summary>
F32 lastUpdateTime = 0.f;
/// <summary>
/// Upon reloading, whether this record has been applied to the avatar.
/// </summary>
bool motionApplied = false;
/// <summary>
/// When reloading, larger numbers are loaded last, nesting order and priority.
/// Represents recaptures.
/// </summary>
int captureOrder = 0;
/// <summary>
/// When reloading, and if not-empty, the names of the bones this motionId should affect.
/// </summary>
std ::string jointNamesAnimated;
};
/// <summary>
/// Gets whether the supplied avatar has ownership of the supplied motion id.
/// </summary>
/// <param name="avatar">The avatar to query for ownership.</param>
/// <param name="motionId">The motion to query for ownership.</param>
/// <returns>True if the avatar has ownership of the motion, otherwise false.</returns>
bool avatarCanUsePose(LLVOAvatar* avatar, LLUUID motionId);
struct compareByCaptureOrder
{
bool operator()(const fsMotionState& a, const fsMotionState& b)
{
if (a.captureOrder < b.captureOrder)
return true; // Ascending order
return false;
}
};
static std::map <LLUUID, std::vector<fsMotionState>> sMotionStates;
static std::map<LLUUID, int> sCaptureOrder;
};
#endif // LL_FSPoseState_H

View File

@ -29,10 +29,11 @@
#include "fsposingmotion.h"
#include "llcharacter.h"
FSPosingMotion::FSPosingMotion(const LLUUID &id) : LLMotion(id)
FSPosingMotion::FSPosingMotion(const LLUUID& id) : LLKeyframeMotion(id)
{
mName = "fs_poser_pose";
mMotionID = id;
mJointMotionList = &dummyMotionList;
}
LLMotion::LLMotionInitStatus FSPosingMotion::onInitialize(LLCharacter *character)
@ -269,6 +270,113 @@ void FSPosingMotion::setJointBvhLock(FSJointPose* joint, bool lockInBvh)
joint->zeroBaseRotation(lockInBvh);
}
bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames)
{
FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast<FSPosingMotion*>(motionToLoad);
if (!motionToLoadAsFsPosingMotion)
return false;
LLJoint::JointPriority priority = motionToLoad->getPriority();
bool motionIsForAllJoints = selectedJointNames.empty();
LLQuaternion rot;
LLVector3 position, scale;
bool hasRotation = false, hasPosition = false, hasScale = false;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
std::string jointName = poserJoint_iter->jointName();
bool motionIsForThisJoint = selectedJointNames.find(jointName) != std::string::npos;
if (!motionIsForAllJoints && !motionIsForThisJoint)
continue;
hasRotation = hasPosition = hasScale = false;
motionToLoadAsFsPosingMotion->getJointStateAtTime(jointName, timeToLoadAt, &hasRotation, &rot, &hasPosition, &position, &hasScale, &scale);
if (hasRotation)
poserJoint_iter->setBaseRotation(rot, priority);
//if (hasPosition)
// poserJoint_iter->setBasePosition(position, priority);
//if (hasScale)
// poserJoint_iter->setBaseScale(scale, priority);
}
return true;
}
void FSPosingMotion::getJointStateAtTime(std::string jointPoseName, F32 timeToLoadAt,
bool* hasRotation, LLQuaternion* jointRotation,
bool* hasPosition, LLVector3* jointPosition,
bool* hasScale, LLVector3* jointScale)
{
if (this == NULL || mJointMotionList == nullptr)
return;
for (U32 i = 0; i < mJointMotionList->getNumJointMotions(); i++)
{
JointMotion* jm = mJointMotionList->getJointMotion(i);
if (!boost::iequals(jointPoseName, jm->mJointName))
continue;
*hasRotation = (jm->mRotationCurve.mNumKeys > 0);
if (hasRotation)
jointRotation->set(jm->mRotationCurve.getValue(timeToLoadAt, mJointMotionList->mDuration));
*hasPosition = (jm->mPositionCurve.mNumKeys > 0);
if (hasPosition)
jointPosition->set(jm->mPositionCurve.getValue(timeToLoadAt, mJointMotionList->mDuration));
*hasScale = (jm->mScaleCurve.mNumKeys > 0);
if (hasScale)
jointScale->set(jm->mScaleCurve.getValue(timeToLoadAt, mJointMotionList->mDuration));
return;
}
}
bool FSPosingMotion::otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames)
{
FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast<FSPosingMotion*>(motionToQuery);
if (!motionToLoadAsFsPosingMotion)
return false;
return motionToLoadAsFsPosingMotion->motionAnimatesJoints(recapturedJointNames);
}
bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames)
{
if (this == NULL || mJointMotionList == nullptr)
return false;
for (U32 i = 0; i < mJointMotionList->getNumJointMotions(); i++)
{
JointMotion* jm = mJointMotionList->getJointMotion(i);
if (recapturedJointNames.find(jm->mJointName) == std::string::npos)
continue;
if (jm->mRotationCurve.mNumKeys > 0)
return true;
}
return false;
}
void FSPosingMotion::resetBonePriority(std::string boneNamesToReset)
{
if (boneNamesToReset.empty())
return;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
std::string jointName = poserJoint_iter->jointName();
if (boneNamesToReset.find(jointName) != std::string::npos)
poserJoint_iter->setJointPriority(LLJoint::LOW_PRIORITY);
}
}
bool FSPosingMotion::vectorsNotQuiteEqual(LLVector3 v1, LLVector3 v2) const
{
if (vectorAxesAlmostEqual(v1.mV[VX], v2.mV[VX]) &&

View File

@ -32,6 +32,7 @@
//-----------------------------------------------------------------------------
#include "llmotion.h"
#include "fsjointpose.h"
#include "llkeyframemotion.h"
#define MIN_REQUIRED_PIXEL_AREA_POSING 500.f
@ -39,10 +40,11 @@
// class FSPosingMotion
//-----------------------------------------------------------------------------
class FSPosingMotion :
public LLMotion
public LLKeyframeMotion
{
public:
FSPosingMotion(const LLUUID &id);
FSPosingMotion(const LLKeyframeMotion& kfm) : LLKeyframeMotion{ kfm } { }
virtual ~FSPosingMotion(){};
public:
@ -132,6 +134,58 @@ public:
/// <param name="lockInBvh">Whether the joint should be locked if exported to BVH.</param>
void setJointBvhLock(FSJointPose* joint, bool lockInBvh);
/// <summary>
/// Loads the rotations of the supplied motion at the supplied time to the base
/// </summary>
/// <param name="motionToLoad">The motion whose joint rotations (etc) we want to copy to this.</param>
/// <param name="timeToLoadAt">The play-time the animation should be advanced to derive the correct joint state.</param>
/// <param name="selectedJointNames">If only some of the joints should be animated by this motion, name them here.</param>
/// <returns></returns>
bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames);
/// <summary>
/// Tries to get the rotation, position and scale for the supplied joint name at the supplied time.
/// </summary>
/// <param name="jointPoseName">The name of the joint. Example: "mPelvis".</param>
/// <param name="timeToLoadAt">The time to get the rotation at.</param>
/// <param name="hasRotation">Output of whether the animation has a rotation for the supplied joint name.</param>
/// <param name="jointRotation">The output rotation of the named joint.</param>
/// <param name="hasPosition">Output of whether the animation has a position for the supplied joint name.</param>
/// <param name="jointPosition">The output position of the named joint.</param>
/// <param name="hasScale">Output of whether the animation has a scale for the supplied joint name.</param>
/// <param name="jointScale">The output scale of the named joint.</param>
/// <remarks>
/// The most significant thing this method does is provide access to protected properties of some other LLPosingMotion.
/// Thus its most common usage would be to access those properties for an arbitrary animation 'from' the poser's instance of one of these.
/// </remarks>
void getJointStateAtTime(std::string jointPoseName, F32 timeToLoadAt, bool* hasRotation, LLQuaternion* jointRotation,
bool* hasPosition, LLVector3* jointPosition, bool* hasScale, LLVector3* jointScale);
/// <summary>
/// Resets the bone priority to zero for the joints named in the supplied string.
/// </summary>
/// <param name="boneNamesToReset">The string containg bone names (like mPelvis).</param>
void resetBonePriority(std::string boneNamesToReset);
/// <summary>
/// Queries whether the supplied motion animates any of the joints named in the supplied string.
/// </summary>
/// <param name="motionToQuery">The motion to query.</param>
/// <param name="recapturedJointNames">A string containing all of the joint names.</param>
/// <returns>True if the motion animates any of the bones named, otherwise false.</returns>
bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames);
/// <summary>
/// Queries whether the this motion animates any of the joints named in the supplied string.
/// </summary>
/// <param name="recapturedJointNames">A string containing all of the joint names.</param>
/// <returns>True if the motion animates any of the bones named, otherwise false.</returns>
/// <remarks>
/// The most significant thing this method does is provide access to protected properties of an LLPosingMotion.
/// Thus its most common usage would be to access those properties for an arbitrary animation.
/// </remarks>
bool motionAnimatesJoints(std::string recapturedJointNames);
private:
/// <summary>
/// The axial difference considered close enough to be the same.
@ -154,6 +208,11 @@ private:
/// </summary>
LLAssetID mMotionID;
/// <summary>
/// Constructor and usage requires this not be NULL.
/// </summary>
JointMotionList dummyMotionList;
/// <summary>
/// The time constant, in seconds, we use for transitioning between one animation-state to another; this affects the 'damping'
/// of motion between changes to a joint. 'Constant' in this context is not a reference to the language-idea of 'const' value.

View File

@ -1838,6 +1838,15 @@ width="430">
<button.commit_callback
function="Poser.BrowseCache"/>
</button>
<loading_indicator
visible="false"
follows="left|top"
height="20"
layout="topleft"
name="progress_indicator"
left_pad="1"
top_delta="0"
width="20" />
<menu_button
height="21"
follows="top|left"
@ -1856,7 +1865,7 @@ width="430">
name="load_poses_button"
left_pad="1"
top_delta="0"
width="95"/>
width="85"/>
<button
height="21"
follows="top|left"
@ -1871,7 +1880,7 @@ width="430">
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
name="save_poses_button"
width="95"
width="85"
top_delta="0"
left_pad="1">
<button.commit_callback