/**
* @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