diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ae16fda157..ee296bb93f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -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 diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index 7594f57960..f5104ea2d5 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -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("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; +} diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index 69c96a769c..79830826b5 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -39,6 +39,7 @@ class LLLineEditor; class LLScrollListCtrl; class LLSliderCtrl; class LLTabContainer; +class FSLoadPoseTimer; /// /// 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 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 diff --git a/indra/newview/fsjointpose.cpp b/indra/newview/fsjointpose.cpp index e1acbce4d0..b0e879545e 100644 --- a/indra/newview/fsjointpose.cpp +++ b/indra/newview/fsjointpose.cpp @@ -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()) diff --git a/indra/newview/fsjointpose.h b/indra/newview/fsjointpose.h index e4b91f29b6..86ddd8522c 100644 --- a/indra/newview/fsjointpose.h +++ b/indra/newview/fsjointpose.h @@ -70,7 +70,8 @@ class FSJointPose /// /// Undoes the last position set, if any. /// - void undoLastChange(); + /// true if the change we un-did was rotational. + bool undoLastChange(); /// /// Undoes the last position set, if any. @@ -104,6 +105,11 @@ class FSJointPose /// void reflectRotation(); + /// + /// Reflects the base rotation of the represented joint left-right. + /// + void reflectBaseRotation(); + /// /// Sets the private rotation of the represented joint to zero. /// @@ -143,6 +149,11 @@ class FSJointPose /// void swapRotationWith(FSJointPose* oppositeJoint); + /// + /// Exchanges the base rotations between two joints. + /// + void swapBaseRotationWith(FSJointPose* oppositeJoint); + /// /// Clones the rotation to this from the supplied joint. /// @@ -165,6 +176,33 @@ class FSJointPose /// The rotation of the public difference between before and after recapture. LLQuaternion recaptureJointAsDelta(bool zeroBase); + /// + /// Sets the base rotation to the supplied rotation if the supplied priority is appropriate. + /// + /// The base rotation to set; zero is ignored. + /// The priority of the base rotation; only priority equal or higher than any prior sets have any effect. + void setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority); + + /// + /// Sets the base position to the supplied position if the supplied priority is appropriate. + /// + /// The base position to set; zero is ignored. + /// The priority of the base rotation; only priority equal or higher than any prior sets have any effect. + void setBasePosition(LLVector3 position, LLJoint::JointPriority priority); + + /// + /// Sets the base scale to the supplied scale if the supplied priority is appropriate. + /// + /// The base scale to set; zero is ignored. + /// The priority of the base rotation; only priority equal or higher than any prior sets have any effect. + void setBaseScale(LLVector3 scale, LLJoint::JointPriority priority); + + /// + /// Sets the priority of the bone to the supplied value. + /// + /// The new priority of the base rotation. + void setJointPriority(LLJoint::JointPriority priority); + /// /// Clears the undo/redo deque. /// @@ -188,6 +226,18 @@ class FSJointPose /// The new state for the world-rotation lock. void setWorldRotationLockState(bool newState); + /// + /// Gets whether the rotation of a joint has been mirrored. + /// + /// True if the joint has been mirrored, otherwise false. + bool getRotationMirrorState() const; + + /// + /// Sets whether the rotation of a joint has been mirrored. + /// + /// The new state for the mirror. + void setRotationMirrorState(bool newState); + /// /// Reverts the position/rotation/scale to their values when the animation begun. /// This treatment is required for certain joints, particularly Collision Volumes and those bones not commonly animated by an AO. @@ -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; + + /// + /// Whether the joint has been mirrored. + /// + /// + /// Used when loading a diff; indicating that the base-rotations, once restored, need to be swapped. + /// + bool mJointRotationIsMirrored = false; /// /// 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: diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index ba91cf356f..b310a064e1 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -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 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(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; } diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 853b15d308..387e60da12 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -28,6 +28,7 @@ #define LL_FSPoserAnimator_H #include "fsposingmotion.h" +#include "fsposestate.h" #include "llvoavatar.h" /// @@ -197,27 +198,27 @@ public: /// const std::vector 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: /// The lock state to apply. void setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState); + /// + /// Gets whether the supplied joint for the supplied avatar has been mirrored. + /// + /// The avatar owning the supplied joint. + /// The joint to query. + /// True if the joint is maintaining a fixed-rotation in world, otherwise false. + bool getRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint) const; + + /// + /// Sets the mirrored status for supplied joint for the supplied avatar. + /// + /// The avatar owning the supplied joint. + /// The joint to query. + /// The mirror state to apply. + void setRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState); + /// /// Determines if the kind of save to perform should be a 'delta' save, or a complete save. /// @@ -690,6 +707,42 @@ public: /// void loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale); + /// + /// Loads the posing state (base rotations) to the supplied avatars posing-motion, from the supplied record. + /// + /// That avatar whose posing state should be loaded. + /// The record to read the posing state from. + /// True if the pose loaded successfully, otherwise false. + /// + /// 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. + /// + bool loadPosingState(LLVOAvatar* avatar, LLSD pose); + + /// + /// Adds the posing state for the supplied avatar to the supplied record. + /// + /// That avatar whose posing state should be written. + /// The record to write the posing state to. + void savePosingState(LLVOAvatar* avatar, LLSD* saveRecord); + + /// + /// Purges and recaptures the pose state for the supplied avatar. + /// + /// The avatar whose pose state is to be recapture. + /// The joints which were recaptured. + void updatePosingState(LLVOAvatar* avatar, std::vector jointsRecaptured); + + /// + /// Traverses the joints and applies reversals to the base rotations if needed. + /// + /// The posing motion whose pose states require updating. + /// + /// Required after restoring a diff. The base rotations will be in their original arrangment. + /// + void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion); + private: /// /// 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). /// static std::map sAvatarIdToRegisteredAnimationId; + + FSPoseState mPosingState; }; #endif // LL_FSPoserAnimator_H diff --git a/indra/newview/fsposestate.cpp b/indra/newview/fsposestate.cpp new file mode 100644 index 0000000000..753e33bfa2 --- /dev/null +++ b/indra/newview/fsposestate.cpp @@ -0,0 +1,249 @@ +#include "fsposestate.h" +#include "llinventorymodel.h" // gInventory + +std::map> FSPoseState::sMotionStates; +std::map 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(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(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::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(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(); +} diff --git a/indra/newview/fsposestate.h b/indra/newview/fsposestate.h new file mode 100644 index 0000000000..b992fb11cc --- /dev/null +++ b/indra/newview/fsposestate.h @@ -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: + /// + /// Captures the current animations posing the supplied avatar and how long they have been playing. + /// + /// The avatar whose animations are to be captured. + /// + /// Only animations owned by the supplied avatar are documented. + /// + void captureMotionStates(LLVOAvatar* avatar); + + /// + /// Updates the stored list of animations posing the avatar. + /// + /// The avatar whose animations are to be captured. + /// The posing motion. + /// The names of the joints being recaptured. + void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured); + + /// + /// Removes all current animation states for the supplied avatar. + /// + /// The avatar whose animations are to be purged. + void purgeMotionStates(LLVOAvatar* avatar); + + /// + /// Writes any documented poses for the supplied avatar to the supplied stream. + /// + /// The avatar whose animations may have been captured. + /// The record to add to. + void writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord); + + /// + /// Restores pose state(s) from the supplied record. + /// + /// The avatar whose animations may have been captured. + /// The posing motion. + /// The record to read from. + void restoreMotionStates(LLVOAvatar* avatar, LLSD pose); + + /// + /// Applies the motion states for the supplied avatar to the supplied motion. + /// + /// The avatar to apply the motion state(s) to. + /// The posing motion to apply the state(s) to. + /// True if all the motion states for the supplied avatar have been applied, otherwise false. + /// + /// 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. + /// + bool applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion); + + void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder); + +private: + /// + /// A class documenting the state of an animation for an avatar. + /// + class fsMotionState + { + public: + /// + /// The avatar ID this record is associated with. + /// + LLUUID avatarId; + + /// + /// The motion ID recorded animating the avatar ID. + /// + LLAssetID motionId; + + /// + /// The play-time the motionId had progressed until the motion was captured. + /// + F32 lastUpdateTime = 0.f; + + /// + /// Upon reloading, whether this record has been applied to the avatar. + /// + bool motionApplied = false; + + /// + /// When reloading, larger numbers are loaded last, nesting order and priority. + /// Represents recaptures. + /// + int captureOrder = 0; + + /// + /// When reloading, and if not-empty, the names of the bones this motionId should affect. + /// + std ::string jointNamesAnimated; + }; + + /// + /// Gets whether the supplied avatar has ownership of the supplied motion id. + /// + /// The avatar to query for ownership. + /// The motion to query for ownership. + /// True if the avatar has ownership of the motion, otherwise false. + 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 > sMotionStates; + static std::map sCaptureOrder; +}; + +#endif // LL_FSPoseState_H diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index 15fc7d6bb8..2ee32f77d1 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -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(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(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]) && diff --git a/indra/newview/fsposingmotion.h b/indra/newview/fsposingmotion.h index 7e7b8fd9f4..0f336b3ba8 100644 --- a/indra/newview/fsposingmotion.h +++ b/indra/newview/fsposingmotion.h @@ -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: /// Whether the joint should be locked if exported to BVH. void setJointBvhLock(FSJointPose* joint, bool lockInBvh); + /// + /// Loads the rotations of the supplied motion at the supplied time to the base + /// + /// The motion whose joint rotations (etc) we want to copy to this. + /// The play-time the animation should be advanced to derive the correct joint state. + /// If only some of the joints should be animated by this motion, name them here. + /// + bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames); + + /// + /// Tries to get the rotation, position and scale for the supplied joint name at the supplied time. + /// + /// The name of the joint. Example: "mPelvis". + /// The time to get the rotation at. + /// Output of whether the animation has a rotation for the supplied joint name. + /// The output rotation of the named joint. + /// Output of whether the animation has a position for the supplied joint name. + /// The output position of the named joint. + /// Output of whether the animation has a scale for the supplied joint name. + /// The output scale of the named joint. + /// + /// 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. + /// + void getJointStateAtTime(std::string jointPoseName, F32 timeToLoadAt, bool* hasRotation, LLQuaternion* jointRotation, + bool* hasPosition, LLVector3* jointPosition, bool* hasScale, LLVector3* jointScale); + + /// + /// Resets the bone priority to zero for the joints named in the supplied string. + /// + /// The string containg bone names (like mPelvis). + void resetBonePriority(std::string boneNamesToReset); + + /// + /// Queries whether the supplied motion animates any of the joints named in the supplied string. + /// + /// The motion to query. + /// A string containing all of the joint names. + /// True if the motion animates any of the bones named, otherwise false. + bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames); + + /// + /// Queries whether the this motion animates any of the joints named in the supplied string. + /// + /// A string containing all of the joint names. + /// True if the motion animates any of the bones named, otherwise false. + /// + /// 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. + /// + bool motionAnimatesJoints(std::string recapturedJointNames); + private: /// /// The axial difference considered close enough to be the same. @@ -154,6 +208,11 @@ private: /// LLAssetID mMotionID; + /// + /// Constructor and usage requires this not be NULL. + /// + JointMotionList dummyMotionList; + /// /// 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. 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 a2d35e8384..f74b357403 100644 --- a/indra/newview/skins/default/xui/en/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/en/floater_fs_poser.xml @@ -1838,6 +1838,15 @@ width="430"> + name="load_poses_button" left_pad="1" top_delta="0" - width="95"/> + width="85"/>