/** * @file fsfloaterposer.h * @brief View Model for posing your (and other) avatar(s). * * $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_FLOATER_POSER_H #define FS_FLOATER_POSER_H #include "llfloater.h" #include "lltoolmgr.h" #include "fsposeranimator.h" #include "fsmaniprotatejoint.h" class FSVirtualTrackpad; class LLButton; class LLCheckBoxCtrl; class LLLineEditor; class LLScrollListCtrl; class LLSliderCtrl; class LLTabContainer; class FSLoadPoseTimer; /// /// Describes how to load a pose file. /// typedef enum E_LoadPoseMethods { ROTATIONS = 1, POSITIONS = 2, SCALES = 3, ROTATIONS_AND_POSITIONS = 4, ROTATIONS_AND_SCALES = 5, POSITIONS_AND_SCALES = 6, ROT_POS_AND_SCALES = 7, HAND_RIGHT = 8, HAND_LEFT = 9, FACE_ONLY = 10, SELECTIVE = 11, SELECTIVE_ROT = 12, } E_LoadPoseMethods; /// /// Describes the columns of the avatars scroll-list. /// typedef enum E_Columns { COL_ICON = 0, COL_NAME = 1, COL_UUID = 2, COL_SAVE = 3, } E_Columns; /// /// A class containing the UI fiddling for the Poser Floater. /// Please don't do LLJoint stuff here, fsposingmotion (the LLMotion derivative) is the class for that. /// class FSFloaterPoser : public LLFloater, public LLEditMenuHandler { friend class LLFloaterReg; FSFloaterPoser(const LLSD &key); public: void updatePosedBones(const std::string& jointName, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale); LLQuaternion getManipGimbalRotation(const std::string& jointName); void selectJointByName(const std::string& jointName); void undo() override { onUndoLastChange(); }; bool canUndo() const override { return true; } void redo() override { onRedoLastChange(); }; bool canRedo() const override { return true; } private: // Helper function to encapsualte save logic void doPoseSave(LLVOAvatar* avatar, const std::string& filename); bool postBuild() override; void onOpen(const LLSD& key) override; void onClose(bool app_quitting) override; void onFocusReceived() override; void onFocusLost() override; virtual void draw() override; /// /// Refreshes the supplied pose list from the supplued subdirectory. /// void refreshPoseScroll(LLScrollListCtrl* posesScrollList, std::optional subDirectory = std::nullopt); /// /// (Dis)Enables all of the posing controls; such as when you can't pose for reasons. /// /// Whether to enable the pose controls. void poseControlsEnable(bool enable); /// /// Refreshes all of the 'joint/bones/thingos' lists (lists-zah, all of them). /// void refreshJointScrollListMembers(); /// /// Adds a 'header' menu item to the supplied scroll list, handy for demarking clusters of joints on the UI. /// /// /// The well-known name of the joint, eg: mChest /// This does a lookup into the poser XML for a friendly header title by joint name, if it exists. /// /// The scroll list to add the header-row to. void addHeaderRowToScrollList(const std::string& jointName, LLScrollListCtrl* bodyJointsScrollList); /// /// Generates the data for a row to add to a scroll-list. /// The supplied joint name is looked up in the UI XML to find a friendly name. /// /// The well-known joint name of the joint to add the row for, eg: mChest. /// Whether the joint is one which should come immediately after a header. /// The data required to make the row. LLSD createRowForJoint(const std::string& jointName, bool isHeaderRow); /// /// Gets the collection of poser joints currently selected on the active bones-tab of the UI. /// /// The selected joints std::vector getUiSelectedPoserJoints() const; /// /// Updates the visual with the first selected joint from the supplied collection, if any. /// /// The collection of selected joints. void updateManipWithFirstSelectedJoint(const std::vector& joints) const; /// /// Gets a detectable avatar by its UUID. /// /// The ID of the avatar to find. /// The avatar, if found, otherwise nullptr. LLVOAvatar* getAvatarByUuid(const LLUUID& avatarToFind) const; /// /// Gets the currently selected avatar or animesh. /// /// The currently selected avatar or animesh. LLVOAvatar* getUiSelectedAvatar() const; /// /// Sets the UI selection for avatar or animesh. /// /// The ID of the avatar to select, if found. void setUiSelectedAvatar(const LLUUID& avatarToSelect); /// /// Gets the current bone-deflection style: encapsulates 'anything else you want to do' while you're manipulating a joint. /// Such as: fiddle the opposite joint too. /// /// A E_BoneDeflectionStyles member. E_BoneDeflectionStyles getUiSelectedBoneDeflectionStyle() const; /// /// Gets the means by which the rotation should be applied to the supplied joint name. /// Such as: fiddle the opposite joint too. /// /// The well-known joint name of the joint to add the row for, eg: mChest. /// A E_RotationStyle member. E_RotationStyle getUiSelectedBoneRotationStyle(const std::string& jointName) const; /// /// Gets the collection of UUIDs for nearby avatars. /// /// A the collection of UUIDs for nearby avatars. uuid_vec_t getNearbyAvatarsAndAnimeshes() const; /// /// Gets whether the supplied character is within chat range of gAgentAvatar. /// /// The character to query whether nearby. /// True if the supplied character is within chat range, otherwise false. bool avatarIsNearbyMe(LLCharacter* character) const; /// /// Gets a collection of UUIDs for avatars currently being presented on the UI. /// /// A the collection of UUIDs. uuid_vec_t getCurrentlyListedAvatarsAndAnimeshes() const; /// /// Gets the scroll-list index of the supplied avatar. /// /// The avatar UUID to find on the avatars scroll list. /// The scroll-list index for the supplied avatar, if found, otherwise -1. S32 getAvatarListIndexForUuid(const LLUUID& toFind) const; /// /// There are several control-callbacks manipulating rotations etc, they all devolve to these. /// In these are the appeals to the posing business layer. /// /// /// Using a set, then a get does not guarantee the value you just set. /// There may be +/- PI difference two axes, because harmonics. /// Thus keep your UI synced with less gets. /// void setSelectedJointsRotation(const LLVector3& absoluteRot, const LLVector3& deltaRot); void setSelectedJointsPosition(F32 x, F32 y, F32 z); void setSelectedJointsScale(F32 x, F32 y, F32 z); /// /// Yeilds the rotation of the first selected joint (one may multi-select). /// /// /// Using a set, then a get does not guarantee the value you just set. /// There may be +/- PI difference two axes, because harmonics. /// Thus keep your UI synced with less gets. /// LLVector3 getRotationOfFirstSelectedJoint() const; LLVector3 getPositionOfFirstSelectedJoint() const; LLVector3 getScaleOfFirstSelectedJoint() const; LLScrollListCtrl* getScrollListForTab(LLPanel* tabPanel) const; // Pose load/save void createUserPoseDirectoryIfNeeded(); void onToggleLoadSavePanel(); void onClickPoseSave(); void onMouseLeaveSavePoseBtn(); void onPoseFileSelect(); bool savePoseToXml(LLVOAvatar* avatar, const std::string& posePath); bool savePoseToBvh(LLVOAvatar* avatar, const std::string& posePath); void onClickBrowsePoseCache(); void onPoseMenuAction(const LLSD& param); bool loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod); bool poseFileStartsFromTeePose(const std::string& poseFileName); S32 tryGetPoseVersion(std::string pathToPoseFile); void setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName(); void setUiSelectedAvatarSaveFileName(const std::string& saveFileName); void timedReload(); void setLoadingProgress(bool started); void startPosingSelf(); void stopPosingAllAvatars(); // visual manipulators control void enableVisualManipulators(); void disableVisualManipulators(); // Visual cue for which bone is under the mouse-cursor void drawOnHoverJointHint(); void markSelectedJointsToHighlight(); // UI Event Handlers void onAvatarsRefresh(); void onAvatarSelect(); void onJointTabSelect(); void onToggleMirrorChange(); void onToggleSympatheticChange(); void onToggleRotationFrameButton(const LLUICtrl* toggleButton); void onToggleVisualManipulators(); void setRotationChangeButtons(bool mirror, bool sympathetic); void onUndoLastChange(); void onRedoLastChange(); void onResetJoint(const LLSD& data); void onSetAvatarToTpose(); void onPoseStartStop(); void onTrackballChanged(); void onYawPitchRollChanged(bool skipUpdateTrackpad = false); void onPositionSet(); void onScaleSet(); void onClickToggleSelectedBoneEnabled(); void onClickRecaptureSelectedBones(); void onClickFlipPose(); void onClickFlipSelectedJoints(); void onAdjustTrackpadSensitivity(); void onClickLoadLeftHandPose(); void onClickLoadRightHandPose(); void onClickLoadHandPose(bool isRightHand); void onClickSavingToBvh(); void onCommitSpinner(const LLUICtrl* spinner, const S32 ID); void onCommitSlider(const LLUICtrl* slider, const S32 id); void onClickSymmetrize(const S32 ID); void onClickLockWorldRotBtn(); // UI Refreshments void refreshRotationSlidersAndSpinners(); void refreshPositionSlidersAndSpinners(); void refreshScaleSlidersAndSpinners(); void refreshTrackpadCursor(); void enableOrDisableRedoAndUndoButton(); /// /// Determines if we have permission to animate the supplied avatar. /// /// The avatar to animate. /// True if we have permission to animate, otherwise false. bool havePermissionToAnimateOtherAvatar(LLVOAvatar* avatar) const; /// /// Determines if we have permission to animate the supplied avatar. /// /// The avatar to animate. /// True if we have permission to animate, otherwise false. bool havePermissionToAnimateAvatar(LLVOAvatar* avatar) const; /// /// Determines if we could animate the supplied avatar. /// /// The avatar to animate. /// True if the avatar is non-null, not dead, in the same region as self, otherwise false. bool couldAnimateAvatar(LLVOAvatar* avatar) const; /// /// Our instance of the class which lets us do the business of manipulating the avatar. /// This separates that business from the code-behind the UI. /// FSPoserAnimator mPoserAnimator; /// /// The supplied Joint name has a quaternion describing its rotation. /// This gets the kind of axial transformation required for 'easy' consumption of the joint's Euler angles on our UI. /// This facilitates 'conceptual' conversion of Euler frame to up/down, left/right and roll and is rather subjective. /// Thus, many of these 'conversions' are backed by values in the XML. /// /// The reference frame for the change. /// The well-known name of the joint, eg: mChest. /// The axial translation so the oily angles make better sense in terms of up/down/left/right/roll. /// /// Euler angles aren't cartesian; they're one of 12 possible orderings or something, yes, yes. /// No the translation isn't untangling all of that, it's not needed until it is. /// We're not landing on Mars with this code, just offering a user reasonable thumb-twiddlings. /// E_BoneAxisTranslation getJointTranslation(E_PoserReferenceFrame frame, const std::string& jointName) const; /// /// Gets the collection of E_BoneAxisNegation values for the supplied joint. /// /// The reference frame for the change. /// The name of the joind to get the axis transformation for. /// The kind of axis transformation to perform. S32 getJointNegation(E_PoserReferenceFrame frame, const std::string& jointName) const; /// /// Gets the reference frame for the rotation/position/scale change. /// /// The reference frame for the change. E_PoserReferenceFrame getReferenceFrame() const; /// /// Gets the axial translation required for joints when saving to BVH. /// /// The name of the joint to get the transformation for. /// The axial translation required. E_BoneAxisTranslation getBvhJointTranslation(const std::string& jointName) const; S32 getBvhJointNegation(const std::string& jointName) const; /// /// Refreshes the text on the avatars scroll list based on their state. /// void refreshTextHighlightingOnAvatarScrollList(); /// /// Gets an appropriate icon for the supplied avatar, based on sharing permission. /// /// The avatar to get an icon for. /// A string with the name of an icon. std::string getIconNameForAvatar(LLVOAvatar* avatar); /// /// Refreshes the text on all joints scroll lists based on their state. /// void refreshTextHighlightingOnJointScrollLists(); /// /// Sets the text of the save pose button. /// /// Whether to indicate a diff will be saved, instead of a pose. void setSavePosesButtonText(bool setAsSaveDiff); /// /// Applies the appropriate font-face (such as bold) to the text of the supplied list, to indicate use. /// /// The name of the list to adjust text-face for. /// The avatar to whom the list is relevant. void addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* avatar); /// /// Gets a string for a joint on a scroll-list. /// /// The avatar owning the supplied joint. /// The joint to query. /// A string naming an icon to present with the joint. std::string getScrollListIconForJoint(LLVOAvatar* avatar, FSPoserAnimator::FSPoserJoint joint); /// /// Tries to get the named string from the XUI. /// /// The name of the string. /// The named string, if it exists, otherwise an empty string. std::string tryGetString(std::string_view name); /// /// Gets the name of an item from the supplied object ID. /// /// The control avatar to get the name for. /// The name of the supplied object. /// /// Getting the name for an arbitrary item appears to involve sending system message and creating a /// callback, making for unwanted dependencies and conflict-risk; so not implemented. /// std::string getControlAvatarName(const LLControlAvatar* avatar); /// Gets whether the pose should also write a BVH file when saved. /// /// True if the user wants to additionally save a BVH file, otherwise false. bool getSavingToBvh() const; /// /// Writes the current pose in BVH-format to the supplied stream. /// /// The stream to write the pose to. /// The avatar whose pose should be saved. /// True if the pose saved successfully as a BVH, otherwise false. /// /// Only joints with a zero base-rotation should export to BVH. /// bool writePoseAsBvh(llofstream* fileStream, LLVOAvatar* avatar); /// /// Recursively writes a fragment of a BVH file format representation of the supplied joint, then that joints BVH child(ren). /// None of what is written here matters a jot; it's just here so it parses on read. /// /// The stream to write the fragment to. /// The avatar owning the supplied joint. /// The joint whose fragment should be written, and whose child(ren) will also be written. /// The number of tab-stops to include for formatting purpose. void writeBvhFragment(llofstream* fileStream, LLVOAvatar* avatar, const FSPoserAnimator::FSPoserJoint* joint, S32 tabStops); /// /// Writes a fragment of the 'single line' representing an animation frame within the BVH file respresenting the positions and/or /// rotations. /// /// The stream to write the position and/or rotation to. /// The avatar owning the supplied joint. /// The joint whose position and/or rotation should be written. void writeBvhMotion(llofstream* fileStream, LLVOAvatar* avatar, const FSPoserAnimator::FSPoserJoint* joint); /// /// Writes a fragment of the 'single line' representing the first animation frame within the BVH file respresenting the positions and/or /// rotations. /// /// The stream to write the position and/or rotation to. /// The joint whose position and/or rotation should be written. void writeFirstFrameOfBvhMotion(llofstream* fileStream, const FSPoserAnimator::FSPoserJoint* joint); /// /// Generates a string with the supplied number of tab-chars. /// std::string static getTabs(S32 numOfTabstops); /// /// Transforms a rotation such that llbvhloader.cpp can resolve it to something vaguely approximating the supplied angle. /// When I say vague, I mean, it's numbers, buuuuut. /// std::string static rotationToString(const LLVector3& val); /// /// Transforms the supplied vector into a string of three numbers, format suiting to writing into a BVH file. /// std::string static positionToString(const LLVector3& val); /// /// Performs an angle module of the supplied value to between -180 & 180 (degrees). /// /// The value to modulo. /// The modulo value. /// /// If the trackpad is in 'infinite scroll' mode, it can produce normalized-values outside the range of the spinners. /// This method ensures whatever value the trackpad produces, they work with the spinners. /// static F32 clipRange(F32 value); LLToolset* mLastToolset{ nullptr }; LLTool* mJointRotTool{ nullptr }; LLVector3 mLastSliderRotation; FSPoserAnimator::FSPoserJoint* mLastSelectedJoint{ nullptr }; U64 timeFadeStartedMicrosec; FSVirtualTrackpad* mAvatarTrackball{ nullptr }; LLSliderCtrl* mTrackpadSensitivitySlider{ nullptr }; LLSliderCtrl* mPosXSlider{ nullptr }; LLSliderCtrl* mPosYSlider{ nullptr }; LLSliderCtrl* mPosZSlider{ nullptr }; LLSliderCtrl* mAdvRotXSlider{ nullptr }; LLSliderCtrl* mAdvRotYSlider{ nullptr }; LLSliderCtrl* mAdvRotZSlider{ nullptr }; LLSliderCtrl* mAdvPosXSlider{ nullptr }; LLSliderCtrl* mAdvPosYSlider{ nullptr }; LLSliderCtrl* mAdvPosZSlider{ nullptr }; LLSliderCtrl* mAdvScaleXSlider{ nullptr }; LLSliderCtrl* mAdvScaleYSlider{ nullptr }; LLSliderCtrl* mAdvScaleZSlider{ nullptr }; LLTabContainer* mJointsTabs{ nullptr }; LLTabContainer* mHandsTabs{ nullptr }; LLScrollListCtrl* mAvatarSelectionScrollList{ nullptr }; LLScrollListCtrl* mBodyJointsScrollList{ nullptr }; LLScrollListCtrl* mFaceJointsScrollList{ nullptr }; LLScrollListCtrl* mHandJointsScrollList{ nullptr }; LLScrollListCtrl* mMiscJointsScrollList{ nullptr }; LLScrollListCtrl* mCollisionVolumesScrollList{ nullptr }; LLScrollListCtrl* mEntireAvJointScroll{ nullptr }; LLScrollListCtrl* mPosesScrollList{ nullptr }; LLScrollListCtrl* mHandPresetsScrollList{ nullptr }; LLButton* mToggleVisualManipulators{ nullptr }; LLButton* mStartStopPosingBtn{ nullptr }; LLButton* mToggleLoadSavePanelBtn{ nullptr }; LLButton* mBrowserFolderBtn{ nullptr }; LLButton* mLoadPosesBtn{ nullptr }; LLButton* mSavePosesBtn{ nullptr }; LLButton* mFlipPoseBtn{ nullptr }; LLButton* mFlipJointBtn{ nullptr }; LLButton* mRecaptureBtn{ nullptr }; LLButton* mTogglePosingBonesBtn{ nullptr }; LLButton* mToggleLockWorldRotBtn{ nullptr }; LLButton* mToggleMirrorRotationBtn{ nullptr }; LLButton* mToggleSympatheticRotationBtn{ nullptr }; LLButton* mToggleDeltaModeBtn{ nullptr }; LLButton* mRedoChangeBtn{ nullptr }; LLButton* mUndoChangeBtn{ nullptr }; LLButton* mSetToTposeButton{ nullptr }; LLButton* mBtnJointRotate{ nullptr }; LLButton* mBtnJointReset{ nullptr }; LLButton* mBtnWorldFrame{ nullptr }; LLButton* mBtnAvatarFrame{ nullptr }; LLButton* mBtnScreenFrame{ nullptr }; LLLineEditor* mPoseSaveNameEditor{ nullptr }; FSLoadPoseTimer* mLoadPoseTimer; LLPanel* mJointsParentPnl{ nullptr }; LLPanel* mTrackballPnl{ nullptr }; LLPanel* mPositionRotationPnl{ nullptr }; LLPanel* mBodyJointsPnl{ nullptr }; LLPanel* mFaceJointsPnl{ nullptr }; LLPanel* mHandsJointsPnl{ nullptr }; LLPanel* mMiscJointsPnl{ nullptr }; LLPanel* mCollisionVolumesPnl{ nullptr }; LLPanel* mPosesLoadSavePnl{ nullptr }; LLPanel* mPositionPnl{ nullptr }; LLPanel* mMoveTabPnl{ nullptr }; LLPanel* mTrackballButtonPnl{ nullptr }; LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr }; LLCheckBoxCtrl* mUnlockPelvisInBvhSaveCbx{ nullptr }; LLUICtrl* mTrackpadSensitivitySpnr{ nullptr }; LLUICtrl* mYawSpnr{ nullptr }; LLUICtrl* mPitchSpnr{ nullptr }; LLUICtrl* mRollSpnr{ nullptr }; LLUICtrl* mUpDownSpnr{ nullptr }; LLUICtrl* mLeftRightSpnr{ nullptr }; LLUICtrl* mInOutSpnr{ nullptr }; LLUICtrl* mAdvPosXSpnr{ nullptr }; LLUICtrl* mAdvPosYSpnr{ nullptr }; LLUICtrl* mAdvPosZSpnr{ nullptr }; LLUICtrl* mScaleXSpnr{ nullptr }; LLUICtrl* mScaleYSpnr{ nullptr }; 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); bool loadCompleteOrFailed() const { return !mAttemptLoading && mLoadAttempts > 0; } void completeLoading() { mAttemptLoading = false; } std::string getPosePath() { return mPoseFullPath; }; E_LoadPoseMethods getLoadMethod() const { return mLoadType; }; private: callback_t mCallback; bool mAttemptLoading = false; E_LoadPoseMethods mLoadType = ROT_POS_AND_SCALES; std::string mPoseFullPath; int mLoadAttempts = 0; const int mMaxLoadAttempts = 5; }; #endif