/** * @file fsjointpose.h * @brief Container for the pose of a joint. * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Phoenix Firestorm Viewer Source Code * Copyright (c) 2024 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 FS_JOINTPPOSE_H #define FS_JOINTPPOSE_H //----------------------------------------------------------------------------- // Header files //----------------------------------------------------------------------------- #include "llmotion.h" //----------------------------------------------------------------------------- // class FSJointPose //----------------------------------------------------------------------------- class FSJointPose { public: /// /// A class encapsulating the positions/rotations for a joint. /// /// The joint this joint pose represents. /// The default usage the joint should have in a pose. /// Whether the supplied joint is a collision volume. FSJointPose(LLJoint* joint, U32 usage, bool isCollisionVolume = false); /// /// Gets the name of the joint. /// std::string jointName() const { return mJointName; } /// /// Gets whether this represents a collision volume. /// /// true if the joint is a collision volume, otherwise false. bool isCollisionVolume() const { return mIsCollisionVolume; } /// /// Gets the 'public' position of the joint. /// LLVector3 getPublicPosition() const { return mCurrentState.mPosition; } /// /// Sets the 'public' position of the joint. /// void setPublicPosition(const LLVector3& pos); /// /// Undoes the last position set, if any. /// /// true if the change we un-did was rotational. bool undoLastChange(); /// /// Undoes the last position set, if any. /// void redoLastChange(); /// /// Resets the joint to its conditions when posing started. /// void resetJoint(); /// /// Gets the 'public' rotation of the joint. /// LLQuaternion getPublicRotation() const { return mCurrentState.mRotation; } /// /// Sets the 'public' rotation of the joint. /// /// Whether to zero the base rotation on setting the supplied rotation. /// The change in rotation to apply. /// /// 'Public rotation' is the amount of rotation the user has added to the initial state. /// Public rotation is what a user may save to an external format (such as BVH). /// This distinguishes 'private' rotation, which is the state inherited from something like a pose in-world. /// If zeroBase is true, we treat rotations as if in BVH mode: user work. /// If zeroBase is false, we treat as NOT BVH: some existing pose and user work. /// void setPublicRotation(bool zeroBase, const LLQuaternion& rot); /// /// Reflects the base and delta rotation of the represented joint left-right. /// void reflectRotation(); /// /// Reflects the base rotation of the represented joint left-right. /// void reflectBaseRotation(); /// /// Sets the private rotation of the represented joint to zero. /// /// Whether the joint should be locked if exported to BVH. void zeroBaseRotation(bool lockInBvh); /// /// Queries whether the represented joint is zero. /// /// True if the represented joint is zero, otherwise false. bool isBaseRotationZero() const; /// /// Gets whether an undo of this joint may be performed. /// /// true if the joint may have a undo applied, otherwise false. bool canPerformUndo() const; /// /// Gets whether a redo of this joint may be performed. /// /// true if the joint may have a redo applied, otherwise false. bool canPerformRedo() const { return mUndoneJointStatesIndex > 0; } /// /// Gets the 'public' scale of the joint. /// LLVector3 getPublicScale() const { return mCurrentState.mScale; } /// /// Sets the 'public' scale of the joint. /// void setPublicScale(const LLVector3& scale); /// /// Exchanges the rotations between two joints. /// void swapRotationWith(FSJointPose* oppositeJoint); /// /// Exchanges the base rotations between two joints. /// void swapBaseRotationWith(FSJointPose* oppositeJoint); /// /// Clones the rotation to this from the supplied joint. /// void cloneRotationFrom(FSJointPose* fromJoint); /// /// Mirrors the rotation to this from the supplied joint. /// void mirrorRotationFrom(FSJointPose* fromJoint); /// /// Resets the beginning properties of the joint this represents. /// void recaptureJoint(); /// /// Recalculates the delta reltive to the base for a new rotation. /// /// Whether to zero the base rotation on setting the supplied rotation. /// The rotation of the supplied joint. /// The position of the supplied joint. /// The scale of the supplied joint. /// The rotation of the public difference between before and after recapture. LLQuaternion updateJointAsDelta(bool zeroBase, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale); /// /// 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(const 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(const 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(const 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. /// void purgeUndoQueue(); /// /// Gets whether the user has specified the base rotation of a joint to be zero. /// /// True if the user performed some action to specify zero rotation as the base, otherwise false. bool userHasSetBaseRotationToZero() const; /// /// Gets whether the rotation of a joint has been 'locked' so that its world rotation can remain constant while parent joints change. /// /// True if the joint is rotationally locked to the world, otherwise false. bool getWorldRotationLockState() const; /// /// Sets whether the world-rotation of a joint has been 'locked' so that as its parent joints change rotation or position, this joint keeps a constant world rotation. /// /// 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. /// void revertJoint(); LLQuaternion getTargetRotation() const { return mCurrentState.getTargetRotation(); } LLVector3 getTargetPosition() const { return mCurrentState.getTargetPosition(); } LLVector3 getTargetScale() const { return mCurrentState.getTargetScale(); } /// /// Gets the pointer to the jointstate for the joint this represents. /// LLPointer getJointState() const { return mJointState; } /// /// Gets whether this joint has been modified this session. /// /// True if the joint has been changed at all, otherwise false. bool getJointModified() const { return mModifiedThisSession; } /// /// Gets the number of the joint represented by this. /// /// The joint number, derived from LLjoint. S32 getJointNumber() const { return mJointNumber; } class FSJointState { public: FSJointState(LLJoint* joint) { mStartingRotation.set(joint->getRotation()); mBaseRotation.set(joint->getRotation()); mBasePosition.set(joint->getPosition()); mBaseScale.set(joint->getScale()); } FSJointState() = default; LLQuaternion getTargetRotation() const { return mRotation * mBaseRotation; } LLVector3 getTargetPosition() const { return mPosition + mBasePosition; } LLVector3 getTargetScale() const { return mScale + mBaseScale; } void reflectRotation() { 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) { 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; mJointRotationIsMirrored = false; mLastChangeWasRotational = true; mBaseRotation.set(mStartingRotation); mRotation.set(LLQuaternion::DEFAULT); mPosition.setZero(); mScale.setZero(); } void zeroBaseRotation() { mBasePriority = LLJoint::LOW_PRIORITY; mBaseRotation = LLQuaternion::DEFAULT; mJointRotationIsMirrored = false; } void revertJointToBase(LLJoint* joint) const { if (!joint) return; joint->setRotation(mBaseRotation); joint->setPosition(mBasePosition); joint->setScale(mBaseScale); } LLQuaternion updateFromJointProperties(bool zeroBase, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale) { LLQuaternion initalPublicRot = mRotation; LLQuaternion invRot = mBaseRotation; invRot.conjugate(); LLQuaternion newPublicRot = rotation * invRot; if (zeroBase) { mUserSpecifiedBaseZero = zeroBase; zeroBaseRotation(); } mRotation.set(newPublicRot); mPosition.set(position - mBasePosition); mScale.set(scale - mBaseScale); return newPublicRot *= ~initalPublicRot; } void resetBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority) { if (mUserSpecifiedBaseZero) return; if (priority < mBasePriority) return; if (rotation == LLQuaternion::DEFAULT) return; mBasePriority = priority; mBaseRotation.set(rotation); } void resetBasePosition(LLVector3 position, LLJoint::JointPriority priority) { if (priority < mBasePriority) return; mBasePriority = priority; mBasePosition.set(position); } void resetBaseScale(LLVector3 scale, LLJoint::JointPriority priority) { if (priority < mBasePriority) return; if (scale.isExactlyZero()) return; mBasePriority = priority; mBaseScale.set(scale); } void setPriority(LLJoint::JointPriority priority) { mBasePriority = priority; } private: FSJointState(FSJointState* state) { mStartingRotation.set(state->mStartingRotation); mBaseRotation.set(state->mBaseRotation); mBasePosition.set(state->mBasePosition); mBaseScale.set(state->mBaseScale); mRotation.set(state->mRotation); mPosition.set(state->mPosition); mScale.set(state->mScale); mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero; mRotationIsWorldLocked = state->mRotationIsWorldLocked; mBasePriority = state->mBasePriority; mJointRotationIsMirrored = state->mJointRotationIsMirrored; mLastChangeWasRotational = state->mLastChangeWasRotational; } public: LLQuaternion mRotation; 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. /// /// /// The base-rotation, representing any 'current animation' state when posing starts, may become zero for several reasons. /// Loading a Pose, editing a rotation intended to save to BVH, or setting to 'T-Pose' being examples. /// If a user intends on creating a BVH, zero-rotation has a special meaning upon upload: the joint is free (is not animated by that BVH). /// This value represents the explicit intent to have that joint be 'free' in BVH (which is sometimes undesireable). /// bool mUserSpecifiedBaseZero = false; private: LLQuaternion mStartingRotation; LLQuaternion mBaseRotation; LLVector3 mBasePosition; LLVector3 mBaseScale; LLJoint::JointPriority mBasePriority = LLJoint::LOW_PRIORITY; }; private: std::string mJointName = ""; // expected to be a match to LLJoint.getName() for a joint implementation. LLPointer mJointState{ nullptr }; /// /// Collision Volumes require special treatment when we stop animating an avatar, as they do not revert to their original state /// natively. /// bool mIsCollisionVolume{ false }; S32 mJointNumber = -1; /// /// Whether this joint has ever been changed by poser. /// bool mModifiedThisSession{ false }; std::deque mLastSetJointStates; size_t mUndoneJointStatesIndex = 0; std::chrono::system_clock::time_point mTimeLastUpdatedCurrentState = std::chrono::system_clock::now(); FSJointState mCurrentState; void addStateToUndo(const FSJointState& stateToAddToUndo); FSJointState undoLastStateChange(const FSJointState& currentState); FSJointState redoLastStateChange(const FSJointState& currentState); }; #endif // FS_JOINTPPOSE_H