diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 552e90cee7..3dcd5ab847 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -146,6 +146,7 @@ set(viewer_SOURCE_FILES fslslbridgerequest.cpp fslslpreproc.cpp fslslpreprocviewer.cpp + fsmaniprotatejoint.cpp fsmoneytracker.cpp fsnamelistavatarmenu.cpp fsnearbychatbarlistener.cpp @@ -960,6 +961,7 @@ set(viewer_HEADER_FILES fslslpreproc.h fslslpreprocviewer.h fsmoneytracker.h + fsmaniprotatejoint.h fsnamelistavatarmenu.h fsnearbychatbarlistener.h fsnearbychatcontrol.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c3cf8ba06c..27b483bece 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -26220,5 +26220,16 @@ Change of this parameter will affect the layout of buttons in notification toast Value 1.0 + FSManipRotateJointUseNaturalDirection + + Comment + use the natural bone direction instead of world rotation + Persist + 1 + Type + Boolean + Value + 1 + diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index 9b0d95ab55..9a54e1d7bf 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -27,6 +27,7 @@ #include "fsfloaterposer.h" #include "fsposeranimator.h" #include "fsvirtualtrackpad.h" +#include "v4color.h" #include "llagent.h" #include "llavatarnamecache.h" #include "llcheckboxctrl.h" @@ -42,6 +43,7 @@ #include "llwindow.h" #include "llvoavatarself.h" #include "llinventoryfunctions.h" +#include "lltoolcomp.h" namespace { @@ -55,7 +57,6 @@ constexpr char XML_LIST_TITLE_STRING_PREFIX[] = "title_"; constexpr char XML_JOINT_TRANSFORM_STRING_PREFIX[] = "joint_transform_"; constexpr char XML_JOINT_DELTAROT_STRING_PREFIX[] = "joint_delta_rotate_"; constexpr char BVH_JOINT_TRANSFORM_STRING_PREFIX[] = "bvh_joint_transform_"; -constexpr std::string_view POSER_ADVANCEDWINDOWSTATE_SAVE_KEY = "FSPoserAdvancedWindowState"; constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpadSensitivity"; constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPosingWhenClosed"; constexpr std::string_view POSER_RESETBASEROTONEDIT_SAVE_KEY = "FSPoserResetBaseRotationOnEdit"; @@ -74,10 +75,10 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) mCommitCallbackRegistrar.add("Poser.RefreshAvatars", [this](LLUICtrl*, const LLSD&) { onAvatarsRefresh(); }); mCommitCallbackRegistrar.add("Poser.StartStopAnimating", [this](LLUICtrl*, const LLSD&) { onPoseStartStop(); }); mCommitCallbackRegistrar.add("Poser.ToggleLoadSavePanel", [this](LLUICtrl*, const LLSD&) { onToggleLoadSavePanel(); }); - mCommitCallbackRegistrar.add("Poser.ToggleAdvancedPanel", [this](LLUICtrl*, const LLSD&) { onToggleAdvancedPanel(); }); + mCommitCallbackRegistrar.add("Poser.ToggleVisualManipulators", [this](LLUICtrl*, const LLSD&) { onToggleVisualManipulators(); }); - mCommitCallbackRegistrar.add("Poser.UndoLastRotation", [this](LLUICtrl*, const LLSD&) { onUndoLastRotation(); }); - mCommitCallbackRegistrar.add("Poser.RedoLastRotation", [this](LLUICtrl*, const LLSD&) { onRedoLastRotation(); }); + mCommitCallbackRegistrar.add("Poser.UndoLastRotation", [this](LLUICtrl*, const LLSD&) { onUndoLastChange(); }); + mCommitCallbackRegistrar.add("Poser.RedoLastRotation", [this](LLUICtrl*, const LLSD&) { onRedoLastChange(); }); mCommitCallbackRegistrar.add("Poser.ToggleMirrorChanges", [this](LLUICtrl*, const LLSD&) { onToggleMirrorChange(); }); mCommitCallbackRegistrar.add("Poser.ToggleSympatheticChanges", [this](LLUICtrl*, const LLSD&) { onToggleSympatheticChange(); }); mCommitCallbackRegistrar.add("Poser.AdjustTrackPadSensitivity", [this](LLUICtrl*, const LLSD&) { onAdjustTrackpadSensitivity(); }); @@ -87,12 +88,9 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) mCommitCallbackRegistrar.add("Poser.Advanced.PositionSet", [this](LLUICtrl*, const LLSD&) { onAdvancedPositionSet(); }); mCommitCallbackRegistrar.add("Poser.Advanced.ScaleSet", [this](LLUICtrl*, const LLSD&) { onAdvancedScaleSet(); }); - mCommitCallbackRegistrar.add("Poser.UndoLastPosition", [this](LLUICtrl*, const LLSD&) { onUndoLastPosition(); }); - mCommitCallbackRegistrar.add("Poser.RedoLastPosition", [this](LLUICtrl*, const LLSD&) { onRedoLastPosition(); }); - mCommitCallbackRegistrar.add("Poser.ResetPosition", [this](LLUICtrl*, const LLSD&) { onResetPosition(); }); - mCommitCallbackRegistrar.add("Poser.ResetScale", [this](LLUICtrl*, const LLSD&) { onResetScale(); }); - mCommitCallbackRegistrar.add("Poser.UndoLastScale", [this](LLUICtrl*, const LLSD&) { onUndoLastScale(); }); - mCommitCallbackRegistrar.add("Poser.RedoLastScale", [this](LLUICtrl*, const LLSD&) { onRedoLastScale(); }); + mCommitCallbackRegistrar.add("Poser.UndoLastPosition", [this](LLUICtrl*, const LLSD&) { onUndoLastChange(); }); + mCommitCallbackRegistrar.add("Poser.RedoLastPosition", [this](LLUICtrl*, const LLSD&) { onRedoLastChange(); }); + mCommitCallbackRegistrar.add("Poser.ResetJoint", [this](LLUICtrl*, const LLSD& data) { onResetJoint(data); }); mCommitCallbackRegistrar.add("Poser.Save", [this](LLUICtrl*, const LLSD&) { onClickPoseSave(); }); mCommitCallbackRegistrar.add("Pose.Menu", [this](LLUICtrl*, const LLSD& data) { onPoseMenuAction(data); }); @@ -104,10 +102,9 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) mCommitCallbackRegistrar.add("Poser.FlipJoint", [this](LLUICtrl*, const LLSD&) { onClickFlipSelectedJoints(); }); mCommitCallbackRegistrar.add("Poser.RecaptureSelectedBones", [this](LLUICtrl*, const LLSD&) { onClickRecaptureSelectedBones(); }); mCommitCallbackRegistrar.add("Poser.TogglePosingSelectedBones", [this](LLUICtrl*, const LLSD&) { onClickToggleSelectedBoneEnabled(); }); - mCommitCallbackRegistrar.add("Poser.PoseJointsReset", [this](LLUICtrl*, const LLSD&) { onPoseJointsReset(); }); - //mCommitCallbackRegistrar.add("Poser.CommitSpinner", [this](LLUICtrl* spinnerControl, const LLSD&) { onCommitSpinner(spinnerControl); }); - mCommitCallbackRegistrar.add("Poser.CommitSpinner", boost::bind(&FSFloaterPoser::onCommitSpinner, this, _1, _2)); + mCommitCallbackRegistrar.add("Poser.CommitSpinner", [this](LLUICtrl* spinner, const LLSD& data) { onCommitSpinner(spinner, data); }); + mCommitCallbackRegistrar.add("Poser.Symmetrize", [this](LLUICtrl*, const LLSD& data) { onClickSymmetrize(data); }); } bool FSFloaterPoser::postBuild() @@ -115,22 +112,13 @@ bool FSFloaterPoser::postBuild() mAvatarTrackball = getChild("limb_rotation"); mAvatarTrackball->setCommitCallback([this](LLUICtrl *, const LLSD &) { onLimbTrackballChanged(); }); - mLimbYawSlider = getChild("limb_yaw"); - mLimbYawSlider->setCommitCallback([this](LLUICtrl *, const LLSD &) { onYawPitchRollSliderChanged(); }); - - mLimbPitchSlider = getChild("limb_pitch"); - mLimbPitchSlider->setCommitCallback([this](LLUICtrl *, const LLSD &) { onYawPitchRollSliderChanged(); }); - - mLimbRollSlider = getChild("limb_roll"); - mLimbRollSlider->setCommitCallback([this](LLUICtrl *, const LLSD &) { onYawPitchRollSliderChanged(); }); - mJointsTabs = getChild("joints_tabs"); mJointsTabs->setCommitCallback( [this](LLUICtrl*, const LLSD&) { onJointTabSelect(); setRotationChangeButtons(false, false); - }); + }); mAvatarSelectionScrollList = getChild("avatarSelection_scroll"); mAvatarSelectionScrollList->setCommitOnSelectionChange(true); @@ -162,9 +150,8 @@ bool FSFloaterPoser::postBuild() mPosesScrollList->setCommitOnSelectionChange(true); mPosesScrollList->setCommitCallback([this](LLUICtrl *, const LLSD &) { onPoseFileSelect(); }); - mToggleAdvancedPanelBtn = getChild("toggleAdvancedPanel"); - if (gSavedSettings.getBOOL(POSER_ADVANCEDWINDOWSTATE_SAVE_KEY)) - mToggleAdvancedPanelBtn->setValue(true); + mToggleVisualManipulators = getChild("toggleVisualManipulators"); + mToggleVisualManipulators->setToggleState(true); mTrackpadSensitivitySlider = getChild("trackpad_sensitivity_slider"); @@ -204,7 +191,6 @@ bool FSFloaterPoser::postBuild() mSetToTposeButton = getChild("set_t_pose_button"); mJointsParentPnl = getChild("joints_parent_panel"); - mAdvancedParentPnl = getChild("advanced_parent_panel"); mTrackballPnl = getChild("trackball_panel"); mPositionRotationPnl = getChild("positionRotation_panel"); mBodyJointsPnl = getChild("body_joints_panel"); @@ -220,8 +206,11 @@ bool FSFloaterPoser::postBuild() mTrackpadSensitivitySpnr = getChild("trackpad_sensitivity_spinner"); mYawSpnr = getChild("limb_yaw_spinner"); + mYawSpnr->setCommitCallback([this](LLUICtrl*, const LLSD&) { onYawPitchRollChanged(); }); mPitchSpnr = getChild("limb_pitch_spinner"); - mRollSpnr = getChild("limb_roll_spinner"); + mPitchSpnr->setCommitCallback([this](LLUICtrl*, const LLSD&) { onYawPitchRollChanged(); }); + mRollSpnr = getChild("limb_roll_spinner"); + mRollSpnr->setCommitCallback([this](LLUICtrl*, const LLSD&) { onYawPitchRollChanged(); }); mUpDownSpnr = getChild("av_position_updown_spinner"); mLeftRightSpnr = getChild("av_position_leftright_spinner"); mInOutSpnr = getChild("av_position_inout_spinner"); @@ -232,6 +221,8 @@ bool FSFloaterPoser::postBuild() mScaleYSpnr = getChild("adv_scaley_spinner"); mScaleZSpnr = getChild("adv_scalez_spinner"); + mBtnJointRotate = getChild("button_joint_rotate_tool"); + return true; } @@ -241,21 +232,54 @@ void FSFloaterPoser::onOpen(const LLSD& key) onAvatarsRefresh(); refreshJointScrollListMembers(); onJointTabSelect(); - onOpenSetAdvancedPanel(); refreshPoseScroll(mHandPresetsScrollList, POSE_PRESETS_HANDS_SUBDIRECTORY); startPosingSelf(); + enableVisualManipulators(); LLFloater::onOpen(key); } + +void FSFloaterPoser::onFocusReceived() +{ + LLEditMenuHandler::gEditMenuHandler = this; +} + +void FSFloaterPoser::onFocusLost() +{ + if( LLEditMenuHandler::gEditMenuHandler == this ) + { + LLEditMenuHandler::gEditMenuHandler = nullptr; + } +} +void FSFloaterPoser::enableVisualManipulators() +{ + if (LLToolMgr::getInstance()->getCurrentToolset() != gCameraToolset) + { + mLastToolset = LLToolMgr::getInstance()->getCurrentToolset(); + } + LLToolMgr::getInstance()->setCurrentToolset(gPoserToolset); + LLToolMgr::getInstance()->getCurrentToolset()->selectTool(FSToolCompPose::getInstance()); + FSToolCompPose::getInstance()->setAvatar( gAgentAvatarp); +} + +void FSFloaterPoser::disableVisualManipulators() +{ + if (mLastToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(mLastToolset); + } + FSToolCompPose::getInstance()->setAvatar(nullptr); +} + void FSFloaterPoser::onClose(bool app_quitting) { - if (mToggleAdvancedPanelBtn) - gSavedSettings.setBOOL(POSER_ADVANCEDWINDOWSTATE_SAVE_KEY, mToggleAdvancedPanelBtn->getValue().asBoolean()); - if (gSavedSettings.getBOOL(POSER_STOPPOSINGWHENCLOSED_SAVE_KEY)) + { stopPosingAllAvatars(); + } + disableVisualManipulators(); LLFloater::onClose(app_quitting); } @@ -324,9 +348,9 @@ void FSFloaterPoser::onPoseFileSelect() bool isDeltaSave = !poseFileStartsFromTeePose(name); if (isDeltaSave) - mLoadPosesBtn->setLabel("Load Diff"); + mLoadPosesBtn->setLabel(getString("LoadDiffLabel")); else - mLoadPosesBtn->setLabel("Load Pose"); + mLoadPosesBtn->setLabel(getString("LoadPoseLabel")); } void FSFloaterPoser::onClickPoseSave() @@ -382,7 +406,7 @@ void FSFloaterPoser::createUserPoseDirectoryIfNeeded() return; auto posesToCopy = gDirUtilp->getFilesInDir(sourcePresetPath); - for (auto pose : posesToCopy) + for (const auto& pose : posesToCopy) { std::string source = sourcePresetPath + gDirUtilp->getDirDelimiter() + pose; std::string destination = userHandPresetsPath + gDirUtilp->getDirDelimiter() + pose; @@ -411,7 +435,7 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi { bool savingDiff = !mPoserAnimator.allBaseRotationsAreZero(avatar); LLSD record; - record["version"]["value"] = (S32)5; + record["version"]["value"] = (S32)6; record["startFromTeePose"]["value"] = !savingDiff; LLVector3 rotation, position, scale, zeroVector; @@ -573,6 +597,33 @@ void FSFloaterPoser::onClickRecaptureSelectedBones() refreshTrackpadCursor(); refreshTextHighlightingOnJointScrollLists(); } +void FSFloaterPoser::updatePosedBones() +{ + auto selectedJoints = getUiSelectedPoserJoints(); + if (selectedJoints.size() < 1) + return; + + LLVOAvatar *avatar = getUiSelectedAvatar(); + if (!avatar) + return; + + if (!mPoserAnimator.isPosingAvatar(avatar)) + return; + + for (auto item : selectedJoints) + { + bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); + if (!currentlyPosing) + continue; + + mPoserAnimator.recaptureJointAsDelta(avatar, *item, getJointTranslation(item->jointName()), getJointNegation(item->jointName())); + } + + setSavePosesButtonText(true); + refreshRotationSlidersAndSpinners(); + refreshTrackpadCursor(); + refreshTextHighlightingOnJointScrollLists(); +} void FSFloaterPoser::onClickBrowsePoseCache() { @@ -582,8 +633,24 @@ void FSFloaterPoser::onClickBrowsePoseCache() gViewerWindow->getWindow()->openFile(pathname); } -//void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner) -// Pass in an ID as a parameter, so you can use a switch statement +void FSFloaterPoser::onClickSymmetrize(S32 ID) +{ + if (notDoubleClicked()) + return; + + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) + return; + + if (!mPoserAnimator.isPosingAvatar(avatar)) + return; + + mPoserAnimator.symmetrizeLeftToRightOrRightToLeft(avatar, ID == 2); + + refreshRotationSlidersAndSpinners(); + refreshTrackpadCursor(); +} + void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner, S32 id) { if (!spinner) @@ -597,8 +664,6 @@ void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner, S32 id) F32 value = (F32)spinner->getValue().asReal(); - // Use the ID passed in to perform a switch statment - // which should make each action take the same amount of time. switch (id) { case 0: // av_position_updown_spinner @@ -624,24 +689,6 @@ void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner, S32 id) onAdjustTrackpadSensitivity(); break; } - case 4: // limb_pitch_spinner - { - mLimbPitchSlider->setValue(value); - onYawPitchRollSliderChanged(); - break; - } - case 5: // limb_yaw_spinner - { - mLimbYawSlider->setValue(value); - onYawPitchRollSliderChanged(); - break; - } - case 6: // limb_roll_spinner - { - mLimbRollSlider->setValue(value); - onYawPitchRollSliderChanged(); - break; - } case 7: // adv_posx_spinner { if (changingBodyPosition) @@ -690,34 +737,6 @@ void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner, S32 id) } } -void FSFloaterPoser::onPoseJointsReset() -{ - if (notDoubleClicked()) - return; - - LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - - if (!mPoserAnimator.isPosingAvatar(avatar)) - return; - - auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) - return; - - for (auto item : selectedJoints) - { - bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.resetAvatarJoint(avatar, *item); - } - - refreshRotationSlidersAndSpinners(); - refreshTrackpadCursor(); - refreshAvatarPositionSlidersAndSpinners(); -} - void FSFloaterPoser::onPoseMenuAction(const LLSD& param) { std::string loadStyle = param.asString(); @@ -757,7 +776,7 @@ void FSFloaterPoser::onPoseMenuAction(const LLSD& param) loadPoseFromXml(avatar, poseName, loadType); onJointTabSelect(); refreshJointScrollListMembers(); - setSavePosesButtonText(mPoserAnimator.allBaseRotationsAreZero(avatar)); + setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar)); } bool FSFloaterPoser::notDoubleClicked() @@ -946,6 +965,9 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose version = (S32)control_map["value"].asInteger(); } + if (version > 5 && startFromZeroRot) + mPoserAnimator.setAllAvatarStartingRotationsToZero(avatar); + bool loadPositionsAndScalesAsDeltas = false; if (version > 3) loadPositionsAndScalesAsDeltas = true; @@ -1018,7 +1040,7 @@ void FSFloaterPoser::startPosingSelf() void FSFloaterPoser::stopPosingAllAvatars() { - if (!gAgentAvatarp || gAgentAvatarp.isNull()) + if (!gAgentAvatarp || gAgentAvatarp.isNull() || !mAvatarSelectionScrollList) return; for (auto listItem : mAvatarSelectionScrollList->getAllData()) @@ -1087,7 +1109,6 @@ bool FSFloaterPoser::havePermissionToAnimateAvatar(LLVOAvatar *avatar) const void FSFloaterPoser::poseControlsEnable(bool enable) { - mAdvancedParentPnl->setEnabled(enable); mTrackballPnl->setEnabled(enable); mFlipPoseBtn->setEnabled(enable); mFlipJointBtn->setEnabled(enable); @@ -1254,7 +1275,7 @@ void FSFloaterPoser::setRotationChangeButtons(bool togglingMirror, bool toggling refreshTrackpadCursor(); } -void FSFloaterPoser::onUndoLastRotation() +void FSFloaterPoser::onUndoLastChange() { LLVOAvatar* avatar = getUiSelectedAvatar(); if (!avatar) @@ -1271,59 +1292,15 @@ void FSFloaterPoser::onUndoLastRotation() { bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); if (currentlyPosing) - mPoserAnimator.undoLastJointRotation(avatar, *item, getUiSelectedBoneDeflectionStyle()); + mPoserAnimator.undoLastJointChange(avatar, *item, getUiSelectedBoneDeflectionStyle()); } enableOrDisableRedoButton(); refreshRotationSlidersAndSpinners(); refreshTrackpadCursor(); -} - -void FSFloaterPoser::onUndoLastPosition() -{ - LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - - if (!mPoserAnimator.isPosingAvatar(avatar)) - return; - - auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) - return; - - for (auto item : selectedJoints) - { - bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.undoLastJointPosition(avatar, *item, getUiSelectedBoneDeflectionStyle()); - } - - refreshAdvancedPositionSlidersAndSpinners(); + refreshPositionSlidersAndSpinners(); refreshAvatarPositionSlidersAndSpinners(); -} - -void FSFloaterPoser::onUndoLastScale() -{ - LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - - if (!mPoserAnimator.isPosingAvatar(avatar)) - return; - - auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) - return; - - for (auto item : selectedJoints) - { - bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.undoLastJointScale(avatar, *item, getUiSelectedBoneDeflectionStyle()); - } - - refreshAdvancedScaleSlidersAndSpinners(); + refreshScaleSlidersAndSpinners(); } void FSFloaterPoser::onSetAvatarToTpose() @@ -1340,11 +1317,13 @@ void FSFloaterPoser::onSetAvatarToTpose() refreshTextHighlightingOnJointScrollLists(); } -void FSFloaterPoser::onResetPosition() +void FSFloaterPoser::onResetJoint(const LLSD data) { if (notDoubleClicked()) return; + int resetType = data.asInteger(); + LLVOAvatar* avatar = getUiSelectedAvatar(); if (!avatar) return; @@ -1359,41 +1338,20 @@ void FSFloaterPoser::onResetPosition() for (auto item : selectedJoints) { bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.resetJointPosition(avatar, *item, getUiSelectedBoneDeflectionStyle()); + if (!currentlyPosing) + continue; + + mPoserAnimator.resetJoint(avatar, *item, getUiSelectedBoneDeflectionStyle()); } - refreshAdvancedPositionSlidersAndSpinners(); + refreshRotationSlidersAndSpinners(); + refreshTrackpadCursor(); refreshAvatarPositionSlidersAndSpinners(); + refreshPositionSlidersAndSpinners(); + refreshScaleSlidersAndSpinners(); } -void FSFloaterPoser::onResetScale() -{ - if (notDoubleClicked()) - return; - - LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - - if (!mPoserAnimator.isPosingAvatar(avatar)) - return; - - auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) - return; - - for (auto item : selectedJoints) - { - bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.resetJointScale(avatar, *item, getUiSelectedBoneDeflectionStyle()); - } - - refreshAdvancedScaleSlidersAndSpinners(); -} - -void FSFloaterPoser::onRedoLastRotation() +void FSFloaterPoser::onRedoLastChange() { LLVOAvatar* avatar = getUiSelectedAvatar(); if (!avatar) @@ -1410,61 +1368,17 @@ void FSFloaterPoser::onRedoLastRotation() { bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); if (currentlyPosing) - mPoserAnimator.redoLastJointRotation(avatar, *item, getUiSelectedBoneDeflectionStyle()); + mPoserAnimator.redoLastJointChange(avatar, *item, getUiSelectedBoneDeflectionStyle()); } enableOrDisableRedoButton(); refreshRotationSlidersAndSpinners(); refreshTrackpadCursor(); -} - -void FSFloaterPoser::onRedoLastPosition() -{ - LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - - if (!mPoserAnimator.isPosingAvatar(avatar)) - return; - - auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) - return; - - for (auto item : selectedJoints) - { - bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.redoLastJointPosition(avatar, *item, getUiSelectedBoneDeflectionStyle()); - } - - refreshAdvancedPositionSlidersAndSpinners(); + refreshScaleSlidersAndSpinners(); + refreshPositionSlidersAndSpinners(); refreshAvatarPositionSlidersAndSpinners(); } -void FSFloaterPoser::onRedoLastScale() -{ - LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - - if (!mPoserAnimator.isPosingAvatar(avatar)) - return; - - auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) - return; - - for (auto item : selectedJoints) - { - bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); - if (currentlyPosing) - mPoserAnimator.redoLastJointScale(avatar, *item, getUiSelectedBoneDeflectionStyle()); - } - - refreshAdvancedScaleSlidersAndSpinners(); -} - void FSFloaterPoser::enableOrDisableRedoButton() { LLVOAvatar* avatar = getUiSelectedAvatar(); @@ -1483,42 +1397,113 @@ void FSFloaterPoser::enableOrDisableRedoButton() { bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item); if (currentlyPosing) - shouldEnableRedoButton |= mPoserAnimator.canRedoJointRotation(avatar, *item); + shouldEnableRedoButton |= mPoserAnimator.canRedoJointChange(avatar, *item); } mRedoChangeBtn->setEnabled(shouldEnableRedoButton); } -void FSFloaterPoser::onOpenSetAdvancedPanel() +void FSFloaterPoser::onToggleVisualManipulators() { - bool advancedPanelExpanded = mToggleAdvancedPanelBtn->getValue().asBoolean(); - if (advancedPanelExpanded) - onToggleAdvancedPanel(); + bool tools_enabled = mToggleVisualManipulators->getValue().asBoolean(); + + if (tools_enabled) + { + enableVisualManipulators(); + } + else + { + disableVisualManipulators(); + } } -void FSFloaterPoser::onToggleAdvancedPanel() +void FSFloaterPoser::selectJointByName(const std::string& jointName) { - if (isMinimized()) - return; + LLTabContainer* tabContainer = mJointsTabs; + std::vector panels = { + mPositionRotationPnl, + mBodyJointsPnl, + mFaceJointsPnl, + mHandsTabs, + mMiscJointsPnl, + mCollisionVolumesPnl + }; + + std::vector scrollLists = { + mEntireAvJointScroll, + mBodyJointsScrollList, + mFaceJointsScrollList, + mHandJointsScrollList, + mMiscJointsScrollList, + mCollisionVolumesScrollList + }; - bool advancedPanelExpanded = mToggleAdvancedPanelBtn->getValue().asBoolean(); + bool found = false; + for (S32 i = 0; i < tabContainer->getTabCount(); ++i) + { + LLPanel* panel = tabContainer->getPanelByIndex(i); + tabContainer->selectTabPanel(panel); - mAdvancedParentPnl->setVisible(advancedPanelExpanded); + // Special handling for Hands tab + if (panel == mHandsTabs) + { + mHandsTabs->selectTabPanel(mHandsJointsPnl); + } - // change the height of the Poser panel - S32 currentHeight = getRect().getHeight(); - S32 advancedPanelHeight = mAdvancedParentPnl->getRect().getHeight(); + for (auto scrollList : scrollLists) + { + scrollList->deselectAllItems(); + } - S32 poserFloaterHeight = advancedPanelExpanded ? currentHeight + advancedPanelHeight : currentHeight - advancedPanelHeight; - S32 poserFloaterWidth = getRect().getWidth(); + auto scrollList = getScrollListForTab(panel); - if (poserFloaterHeight < 0) - return; - - reshape(poserFloaterWidth, poserFloaterHeight); - onJointTabSelect(); + std::vector items = scrollList->getAllData(); + for (auto item : items) + { + auto* userData = static_cast(item->getUserdata()); + if (userData && userData->jointName() == jointName) + { + tabContainer->selectTab(i); + scrollList->selectNthItem(scrollList->getItemIndex(item)); + scrollList->scrollToShowSelected(); + getUiSelectedPoserJoints(); + return; // Exit the loop once we've found and selected the joint + } + } + } + LL_WARNS() << "Joint not found: " << jointName << LL_ENDL; } +LLScrollListCtrl* FSFloaterPoser::getScrollListForTab(LLPanel * tabPanel) const +{ + if (tabPanel == mPositionRotationPnl) + { + return mEntireAvJointScroll; + } + else if (tabPanel == mBodyJointsPnl) + { + return mBodyJointsScrollList; + } + else if (tabPanel == mFaceJointsPnl) + { + return mFaceJointsScrollList; + } + else if (tabPanel == mHandsTabs) + { + return mHandJointsScrollList; + } + else if (tabPanel == mMiscJointsPnl) + { + return mMiscJointsScrollList; + } + else if (tabPanel == mCollisionVolumesPnl) + { + return mCollisionVolumesScrollList; + } + + LL_WARNS() << "Unknown tab panel: " << tabPanel << LL_ENDL; + return nullptr; +} std::vector FSFloaterPoser::getUiSelectedPoserJoints() const { std::vector joints; @@ -1530,42 +1515,20 @@ std::vector FSFloaterPoser::getUiSelectedPoserJo } LLScrollListCtrl* scrollList{ nullptr }; - + + scrollList = getScrollListForTab(activeTab); if (activeTab == mPositionRotationPnl) { mEntireAvJointScroll->selectFirstItem(); - scrollList = mEntireAvJointScroll; } - else if (activeTab == mBodyJointsPnl) - { - scrollList = mBodyJointsScrollList; - } - else if (activeTab == mFaceJointsPnl) - { - scrollList = mFaceJointsScrollList; - } - else if (activeTab == mHandsTabs) + else if (activeTab == mHandsTabs ) { auto activeHandsSubTab = mHandsTabs->getCurrentPanel(); if (!activeHandsSubTab) { return joints; } - - if (activeHandsSubTab == mHandsJointsPnl) - { - scrollList = mHandJointsScrollList; - } } - else if (activeTab == mMiscJointsPnl) - { - scrollList = mMiscJointsScrollList; - } - else if (activeTab == mCollisionVolumesPnl) - { - scrollList = mCollisionVolumesScrollList; - } - if (!scrollList) { return joints; @@ -1579,7 +1542,18 @@ std::vector FSFloaterPoser::getUiSelectedPoserJo joints.push_back(userData); } } - + auto avatarp = getUiSelectedAvatar(); + if (avatarp) + { + if(joints.size() >= 1) + { + FSToolCompPose::getInstance()->setJoint( gAgentAvatarp->getJoint( JointKey::construct(joints[0]->jointName())) ); + } + else + { + FSToolCompPose::getInstance()->setJoint( nullptr ); + } + } return joints; } @@ -1733,7 +1707,7 @@ void FSFloaterPoser::onAvatarPositionSet() mUpDownSpnr->setValue(posZ); setSelectedJointsPosition(posX, posY, posZ); - refreshAdvancedPositionSlidersAndSpinners(); + refreshPositionSlidersAndSpinners(); } void FSFloaterPoser::onLimbTrackballChanged() @@ -1769,13 +1743,9 @@ void FSFloaterPoser::onLimbTrackballChanged() // as tempting as it is to refactor the following to refreshRotationSliders(), don't. // getRotationOfFirstSelectedJoint/setSelectedJointsRotation are // not necessarily symmetric functions (see their remarks). - mLimbYawSlider->setValue(trackPadPos.mV[VX] *= RAD_TO_DEG); - mLimbPitchSlider->setValue(trackPadPos.mV[VY] *= RAD_TO_DEG); - mLimbRollSlider->setValue(trackPadPos.mV[VZ] *= RAD_TO_DEG); - - mYawSpnr->setValue(mLimbYawSlider->getValueF32()); - mPitchSpnr->setValue(mLimbPitchSlider->getValueF32()); - mRollSpnr->setValue(mLimbRollSlider->getValueF32()); + mYawSpnr->setValue(trackPadPos.mV[VX] *= RAD_TO_DEG); + mPitchSpnr->setValue(trackPadPos.mV[VY] *= RAD_TO_DEG); + mRollSpnr->setValue(trackPadPos.mV[VZ] *= RAD_TO_DEG); } F32 FSFloaterPoser::unWrapScale(F32 scale) @@ -1792,12 +1762,12 @@ F32 FSFloaterPoser::unWrapScale(F32 scale) return result; } -void FSFloaterPoser::onYawPitchRollSliderChanged() +void FSFloaterPoser::onYawPitchRollChanged() { LLVector3 absoluteRotation, deltaRotation; - absoluteRotation.mV[VX] = mLimbYawSlider->getValueF32() * DEG_TO_RAD; - absoluteRotation.mV[VY] = mLimbPitchSlider->getValueF32() * DEG_TO_RAD; - absoluteRotation.mV[VZ] = mLimbRollSlider->getValueF32() * DEG_TO_RAD; + absoluteRotation.mV[VX] = (F32)mYawSpnr->getValue().asReal() * DEG_TO_RAD; + absoluteRotation.mV[VY] = (F32)mPitchSpnr->getValue().asReal() * DEG_TO_RAD; + absoluteRotation.mV[VZ] = (F32)mRollSpnr->getValue().asReal() * DEG_TO_RAD; deltaRotation = absoluteRotation - mLastSliderRotation; mLastSliderRotation = absoluteRotation; @@ -1817,10 +1787,6 @@ void FSFloaterPoser::onYawPitchRollSliderChanged() absoluteRotation.mV[VZ] /= NormalTrackpadRangeInRads; mAvatarTrackball->setValue(absoluteRotation.getValue()); - - mYawSpnr->setValue(mLimbYawSlider->getValueF32()); - mPitchSpnr->setValue(mLimbPitchSlider->getValueF32()); - mRollSpnr->setValue(mLimbRollSlider->getValueF32()); } void FSFloaterPoser::onAdjustTrackpadSensitivity() @@ -1830,9 +1796,9 @@ void FSFloaterPoser::onAdjustTrackpadSensitivity() void FSFloaterPoser::refreshTrackpadCursor() { - F32 axis1 = mLimbYawSlider->getValueF32() * DEG_TO_RAD / NormalTrackpadRangeInRads; - F32 axis2 = mLimbPitchSlider->getValueF32() * DEG_TO_RAD / NormalTrackpadRangeInRads; - F32 axis3 = mLimbRollSlider->getValueF32() * DEG_TO_RAD / NormalTrackpadRangeInRads; + F32 axis1 = (F32)mYawSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads; + F32 axis2 = (F32)mPitchSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads; + F32 axis3 = (F32)mRollSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads; F32 trackPadSensitivity = llmax(gSavedSettings.getF32(POSER_TRACKPAD_SENSITIVITY_SAVE_KEY), 0.0001f); axis1 /= trackPadSensitivity; @@ -1867,15 +1833,12 @@ void FSFloaterPoser::refreshRotationSlidersAndSpinners() LLVector3 rotation = getRotationOfFirstSelectedJoint(); mLastSliderRotation = rotation; - mLimbYawSlider->setValue(rotation.mV[VX] *= RAD_TO_DEG); - mYawSpnr->setValue(rotation.mV[VX]); - mLimbPitchSlider->setValue(rotation.mV[VY] *= RAD_TO_DEG); - mPitchSpnr->setValue(rotation.mV[VY]); - mLimbRollSlider->setValue(rotation.mV[VZ] *= RAD_TO_DEG); - mRollSpnr->setValue(rotation.mV[VZ]); + mYawSpnr->setValue(rotation.mV[VX] *= RAD_TO_DEG); + mPitchSpnr->setValue(rotation.mV[VY] *= RAD_TO_DEG); + mRollSpnr->setValue(rotation.mV[VZ] *= RAD_TO_DEG); } -void FSFloaterPoser::refreshAdvancedPositionSlidersAndSpinners() +void FSFloaterPoser::refreshPositionSlidersAndSpinners() { LLVector3 position = getPositionOfFirstSelectedJoint(); @@ -1887,7 +1850,7 @@ void FSFloaterPoser::refreshAdvancedPositionSlidersAndSpinners() mAdvPosZSpnr->setValue(position.mV[VZ]); } -void FSFloaterPoser::refreshAdvancedScaleSlidersAndSpinners() +void FSFloaterPoser::refreshScaleSlidersAndSpinners() { LLVector3 rotation = getScaleOfFirstSelectedJoint(); @@ -2041,16 +2004,12 @@ LLVector3 FSFloaterPoser::getScaleOfFirstSelectedJoint() const void FSFloaterPoser::onJointTabSelect() { refreshAvatarPositionSlidersAndSpinners(); - refreshRotationSlidersAndSpinners(); + refreshRotationSlidersAndSpinners(); refreshTrackpadCursor(); enableOrDisableRedoButton(); + refreshPositionSlidersAndSpinners(); + refreshScaleSlidersAndSpinners(); onClickSetBaseRotZero(); - - if (mToggleAdvancedPanelBtn->getValue().asBoolean()) - { - refreshAdvancedPositionSlidersAndSpinners(); - refreshAdvancedScaleSlidersAndSpinners(); - } } E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& jointName) const @@ -2110,6 +2069,10 @@ S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const void FSFloaterPoser::onAvatarSelect() { LLVOAvatar* avatar = getUiSelectedAvatar(); + if(avatar) + { + FSToolCompPose::getInstance()->setAvatar(avatar); + } mStartStopPosingBtn->setEnabled(couldAnimateAvatar(avatar)); bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar); diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index 0dbe809af7..3d21953b05 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -1,5 +1,5 @@ /** - * @file fsfloaterposer.cpp + * @file fsfloaterposer.h * @brief View Model for posing your (and other) avatar(s). * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ @@ -29,6 +29,7 @@ #define FS_FLOATER_POSER_H #include "llfloater.h" +#include "lltoolmgr.h" #include "fsposeranimator.h" class FSVirtualTrackpad; @@ -73,16 +74,23 @@ typedef enum 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 +class FSFloaterPoser : public LLFloater, public LLEditMenuHandler { friend class LLFloaterReg; FSFloaterPoser(const LLSD &key); - +public: + void updatePosedBones(); + 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: bool postBuild() override; void onOpen(const LLSD& key) override; void onClose(bool app_quitting) override; - + void onFocusReceived() override; + void onFocusLost() override; /// /// Refreshes the supplied pose list from the supplued subdirectory. /// @@ -176,7 +184,7 @@ class FSFloaterPoser : public LLFloater /// 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. @@ -185,11 +193,10 @@ class FSFloaterPoser : public LLFloater /// 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). /// @@ -202,6 +209,7 @@ class FSFloaterPoser : public LLFloater LLVector3 getPositionOfFirstSelectedJoint() const; LLVector3 getScaleOfFirstSelectedJoint() const; + LLScrollListCtrl* getScrollListForTab(LLPanel * tabPanel) const; // Pose load/save void createUserPoseDirectoryIfNeeded(); void onToggleLoadSavePanel(); @@ -215,30 +223,28 @@ class FSFloaterPoser : public LLFloater bool poseFileStartsFromTeePose(const std::string& poseFileName); void setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName(); void setUiSelectedAvatarSaveFileName(const std::string& saveFileName); + // visual manipulators control + void enableVisualManipulators(); + void disableVisualManipulators(); // UI Event Handlers: void onAvatarsRefresh(); void onAvatarSelect(); void onJointTabSelect(); - void onToggleAdvancedPanel(); void onToggleMirrorChange(); void onToggleSympatheticChange(); + void onToggleVisualManipulators(); void setRotationChangeButtons(bool mirror, bool sympathetic); - void onUndoLastRotation(); - void onRedoLastRotation(); - void onUndoLastPosition(); - void onRedoLastPosition(); - void onUndoLastScale(); - void onRedoLastScale(); - void onResetPosition(); - void onResetScale(); + void onUndoLastChange(); + void onRedoLastChange(); + void onResetJoint(const LLSD data); void onSetAvatarToTpose(); void enableOrDisableRedoButton(); void onPoseStartStop(); void startPosingSelf(); void stopPosingAllAvatars(); void onLimbTrackballChanged(); - void onYawPitchRollSliderChanged(); + void onYawPitchRollChanged(); void onAvatarPositionSet(); void onAdvancedPositionSet(); void onAdvancedScaleSet(); @@ -246,22 +252,20 @@ class FSFloaterPoser : public LLFloater void onClickRecaptureSelectedBones(); void onClickFlipPose(); void onClickFlipSelectedJoints(); - void onPoseJointsReset(); - void onOpenSetAdvancedPanel(); void onAdjustTrackpadSensitivity(); void onClickLoadLeftHandPose(); void onClickLoadRightHandPose(); void onClickLoadHandPose(bool isRightHand); void onClickSetBaseRotZero(); - //void onCommitSpinner(LLUICtrl* spinner); void onCommitSpinner(LLUICtrl* spinner, S32 ID); + void onClickSymmetrize(S32 ID); // UI Refreshments void refreshRotationSlidersAndSpinners(); void refreshAvatarPositionSlidersAndSpinners(); void refreshTrackpadCursor(); - void refreshAdvancedPositionSlidersAndSpinners(); - void refreshAdvancedScaleSlidersAndSpinners(); + void refreshPositionSlidersAndSpinners(); + void refreshScaleSlidersAndSpinners(); /// /// Determines if we have permission to animate the supplied avatar. @@ -443,13 +447,14 @@ class FSFloaterPoser : public LLFloater /// static F32 unWrapScale(F32 scale); + LLToolset* mLastToolset{ nullptr }; + LLTool* mJointRotTool{ nullptr }; + LLVector3 mLastSliderRotation; + FSVirtualTrackpad* mAvatarTrackball{ nullptr }; LLSliderCtrl* mTrackpadSensitivitySlider{ nullptr }; - LLSliderCtrl* mLimbYawSlider{ nullptr }; - LLSliderCtrl* mLimbPitchSlider{ nullptr }; // pointing your nose up or down - LLSliderCtrl* mLimbRollSlider{ nullptr }; // your ear touches your shoulder LLSliderCtrl* mPosXSlider{ nullptr }; LLSliderCtrl* mPosYSlider{ nullptr }; LLSliderCtrl* mPosZSlider{ nullptr }; @@ -473,7 +478,7 @@ class FSFloaterPoser : public LLFloater LLScrollListCtrl* mPosesScrollList{ nullptr }; LLScrollListCtrl* mHandPresetsScrollList{ nullptr }; - LLButton* mToggleAdvancedPanelBtn{ nullptr }; + LLButton* mToggleVisualManipulators{ nullptr }; LLButton* mStartStopPosingBtn{ nullptr }; LLButton* mToggleLoadSavePanelBtn{ nullptr }; LLButton* mBrowserFolderBtn{ nullptr }; @@ -488,10 +493,10 @@ class FSFloaterPoser : public LLFloater LLButton* mToggleDeltaModeBtn{ nullptr }; LLButton* mRedoChangeBtn{ nullptr }; LLButton* mSetToTposeButton{ nullptr }; + LLButton* mBtnJointRotate{ nullptr }; LLLineEditor* mPoseSaveNameEditor{ nullptr }; - LLPanel* mAdvancedParentPnl{ nullptr }; LLPanel* mJointsParentPnl{ nullptr }; LLPanel* mTrackballPnl{ nullptr }; LLPanel* mPositionRotationPnl{ nullptr }; diff --git a/indra/newview/fsjointpose.cpp b/indra/newview/fsjointpose.cpp index 285e1c5322..53a6b2869e 100644 --- a/indra/newview/fsjointpose.cpp +++ b/indra/newview/fsjointpose.cpp @@ -30,7 +30,7 @@ #include "llcharacter.h" /// -/// The maximum length of any undo queue; adding new members preens older ones. +/// The maximum length of the undo queue; adding new members preens older ones. /// constexpr size_t MaximumUndoQueueLength = 20; @@ -48,103 +48,86 @@ FSJointPose::FSJointPose(LLJoint* joint, U32 usage, bool isCollisionVolume) mJointName = joint->getName(); mIsCollisionVolume = isCollisionVolume; - mRotation = FSJointRotation(joint->getRotation()); - mBeginningPosition = joint->getPosition(); - mBeginningScale = joint->getScale(); + mCurrentState = FSJointState(joint); } -void FSJointPose::setPositionDelta(const LLVector3& pos) +void FSJointPose::setPublicPosition(const LLVector3& pos) { - addToUndo(mPositionDelta, &mUndonePositionIndex, &mLastSetPositionDeltas, &mTimeLastUpdatedPosition); - mPositionDelta.set(pos); + addStateToUndo(FSJointState(mCurrentState)); + mCurrentState.mPosition.set(pos); } -void FSJointPose::setRotationDelta(const LLQuaternion& rot) +void FSJointPose::setPublicRotation(const LLQuaternion& rot) { - addToUndo(mRotation, &mUndoneRotationIndex, &mLastSetRotationDeltas, &mTimeLastUpdatedRotation); - mRotation = FSJointRotation(mRotation.baseRotation, rot); + addStateToUndo(FSJointState(mCurrentState)); + mCurrentState.mRotation.set(rot); } -void FSJointPose::setScaleDelta(const LLVector3& scale) +void FSJointPose::setPublicScale(const LLVector3& scale) { - addToUndo(mScaleDelta, &mUndoneScaleIndex, &mLastSetScaleDeltas, &mTimeLastUpdatedScale); - mScaleDelta.set(scale); + addStateToUndo(FSJointState(mCurrentState)); + mCurrentState.mScale.set(scale); } -void FSJointPose::undoLastPositionChange() +void FSJointPose::undoLastChange() { - mPositionDelta.set(undoLastChange(mPositionDelta, &mUndonePositionIndex, &mLastSetPositionDeltas)); + mCurrentState = undoLastStateChange(FSJointState(mCurrentState)); } -void FSJointPose::undoLastRotationChange() +void FSJointPose::redoLastChange() { - mRotation.set(undoLastChange(mRotation, &mUndoneRotationIndex, &mLastSetRotationDeltas)); + mCurrentState = redoLastStateChange(FSJointState(mCurrentState)); } -void FSJointPose::undoLastScaleChange() { mScaleDelta.set(undoLastChange(mScaleDelta, &mUndoneScaleIndex, &mLastSetScaleDeltas)); } - -void FSJointPose::redoLastPositionChange() +void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo) { - mPositionDelta.set(redoLastChange(mPositionDelta, &mUndonePositionIndex, &mLastSetPositionDeltas)); -} - -void FSJointPose::redoLastRotationChange() -{ - mRotation.set(redoLastChange(mRotation, &mUndoneRotationIndex, &mLastSetRotationDeltas)); -} - -void FSJointPose::redoLastScaleChange() { mScaleDelta.set(redoLastChange(mScaleDelta, &mUndoneScaleIndex, &mLastSetScaleDeltas)); } - -template -inline void FSJointPose::addToUndo(T delta, size_t* undoIndex, std::deque* dequeue, - std::chrono::system_clock::time_point* timeLastUpdated) -{ - auto timeIntervalSinceLastChange = std::chrono::system_clock::now() - *timeLastUpdated; - *timeLastUpdated = std::chrono::system_clock::now(); + auto timeIntervalSinceLastChange = std::chrono::system_clock::now() - mTimeLastUpdatedCurrentState; + mTimeLastUpdatedCurrentState = std::chrono::system_clock::now(); if (timeIntervalSinceLastChange < UndoUpdateInterval) return; - if (*undoIndex > 0) + if (mUndoneJointStatesIndex > 0) { - for (size_t i = 0; i < *undoIndex; i++) - dequeue->pop_front(); + for (size_t i = 0; i <= mUndoneJointStatesIndex; i++) + if (!mLastSetJointStates.empty()) + mLastSetJointStates.pop_front(); - *undoIndex = 0; + mUndoneJointStatesIndex = 0; } - dequeue->push_front(delta); + mLastSetJointStates.push_front(stateToAddToUndo); - while (dequeue->size() > MaximumUndoQueueLength) - dequeue->pop_back(); + while (mLastSetJointStates.size() > MaximumUndoQueueLength) + mLastSetJointStates.pop_back(); } -template T FSJointPose::undoLastChange(T thingToSet, size_t* undoIndex, std::deque* dequeue) +FSJointPose::FSJointState FSJointPose::undoLastStateChange(FSJointState thingToSet) { - if (dequeue->empty()) + if (mLastSetJointStates.empty()) return thingToSet; - if (*undoIndex == 0) - dequeue->push_front(thingToSet); + if (mUndoneJointStatesIndex == 0) + mLastSetJointStates.push_front(thingToSet); - *undoIndex += 1; - *undoIndex = llclamp(*undoIndex, 0, dequeue->size() - 1); + mUndoneJointStatesIndex += 1; + mUndoneJointStatesIndex = llclamp(mUndoneJointStatesIndex, 0, mLastSetJointStates.size() - 1); - return dequeue->at(*undoIndex); + return mLastSetJointStates.at(mUndoneJointStatesIndex); } -template T FSJointPose::redoLastChange(T thingToSet, size_t* undoIndex, std::deque* dequeue) +FSJointPose::FSJointState FSJointPose::redoLastStateChange(FSJointState thingToSet) { - if (dequeue->empty()) + if (mLastSetJointStates.empty()) return thingToSet; - if (*undoIndex == 0) + if (mUndoneJointStatesIndex == 0) return thingToSet; - *undoIndex -= 1; - *undoIndex = llclamp(*undoIndex, 0, dequeue->size() - 1); - T result = dequeue->at(*undoIndex); - if (*undoIndex == 0) - dequeue->pop_front(); + mUndoneJointStatesIndex -= 1; + mUndoneJointStatesIndex = llclamp(mUndoneJointStatesIndex, 0, mLastSetJointStates.size() - 1); + auto result = mLastSetJointStates.at(mUndoneJointStatesIndex); + if (mUndoneJointStatesIndex == 0) + mLastSetJointStates.pop_front(); return result; } @@ -158,8 +141,24 @@ void FSJointPose::recaptureJoint() if (!joint) return; - addToUndo(mRotation, &mUndoneRotationIndex, &mLastSetRotationDeltas, &mTimeLastUpdatedRotation); - mRotation = FSJointRotation(joint->getRotation()); + addStateToUndo(FSJointState(mCurrentState)); + mCurrentState = FSJointState(joint); +} +void FSJointPose::recaptureJointAsDelta() +{ + if (mIsCollisionVolume) + { + return; + } + + LLJoint* joint = mJointState->getJoint(); + if (!joint) + { + return; + } + + addStateToUndo(mCurrentState); + mCurrentState = FSJointState(joint); } void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint) @@ -169,9 +168,9 @@ void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint) if (mIsCollisionVolume) return; - auto tempRot = FSJointRotation(mRotation); - mRotation = FSJointRotation(oppositeJoint->mRotation); - oppositeJoint->mRotation = tempRot; + auto tempState = FSJointState(mCurrentState); + mCurrentState.cloneRotationFrom(oppositeJoint->mCurrentState); + oppositeJoint->mCurrentState.cloneRotationFrom(tempState); } void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint) @@ -179,8 +178,8 @@ void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint) if (!fromJoint) return; - addToUndo(mRotation, &mUndoneRotationIndex, &mLastSetRotationDeltas, &mTimeLastUpdatedRotation); - mRotation = FSJointRotation(fromJoint->mRotation); + addStateToUndo(FSJointState(mCurrentState)); + mCurrentState.cloneRotationFrom(fromJoint->mCurrentState); } void FSJointPose::mirrorRotationFrom(FSJointPose* fromJoint) @@ -189,22 +188,12 @@ void FSJointPose::mirrorRotationFrom(FSJointPose* fromJoint) return; cloneRotationFrom(fromJoint); - - mRotation.baseRotation = LLQuaternion(-mRotation.baseRotation.mQ[VX], mRotation.baseRotation.mQ[VY], -mRotation.baseRotation.mQ[VZ], - mRotation.baseRotation.mQ[VW]); - mRotation.deltaRotation = LLQuaternion(-mRotation.deltaRotation.mQ[VX], mRotation.deltaRotation.mQ[VY], -mRotation.deltaRotation.mQ[VZ], - mRotation.deltaRotation.mQ[VW]); + mCurrentState.reflectRotation(); } void FSJointPose::revertJoint() { - LLJoint* joint = mJointState->getJoint(); - if (!joint) - return; - - joint->setRotation(mRotation.baseRotation); - joint->setPosition(mBeginningPosition); - joint->setScale(mBeginningScale); + mCurrentState.revertJointToBase(mJointState->getJoint()); } void FSJointPose::reflectRotation() @@ -212,7 +201,7 @@ void FSJointPose::reflectRotation() if (mIsCollisionVolume) return; - mRotation.reflectRotation(); + mCurrentState.reflectRotation(); } void FSJointPose::zeroBaseRotation() @@ -220,7 +209,7 @@ void FSJointPose::zeroBaseRotation() if (mIsCollisionVolume) return; - mRotation.baseRotation = LLQuaternion::DEFAULT; + mCurrentState.zeroBaseRotation(); } bool FSJointPose::isBaseRotationZero() const @@ -228,5 +217,5 @@ bool FSJointPose::isBaseRotationZero() const if (mIsCollisionVolume) return true; - return mRotation.baseRotation == LLQuaternion::DEFAULT; + return mCurrentState.baseRotationIsZero(); } diff --git a/indra/newview/fsjointpose.h b/indra/newview/fsjointpose.h index 1b50f908bd..fe2d200bd1 100644 --- a/indra/newview/fsjointpose.h +++ b/indra/newview/fsjointpose.h @@ -58,34 +58,39 @@ class FSJointPose bool isCollisionVolume() const { return mIsCollisionVolume; } /// - /// Gets the position change the animator wishes the joint to have. + /// Gets the 'public' position of the joint. /// - LLVector3 getPositionDelta() const { return mPositionDelta; } + LLVector3 getPublicPosition() const { return mCurrentState.mPosition; } /// - /// Sets the position the animator wishes the joint to be in. + /// Sets the 'public' position of the joint. /// - void setPositionDelta(const LLVector3& pos); + void setPublicPosition(const LLVector3& pos); /// /// Undoes the last position set, if any. /// - void undoLastPositionChange(); + void undoLastChange(); /// /// Undoes the last position set, if any. /// - void redoLastPositionChange(); + void redoLastChange(); /// - /// Gets the rotation the animator wishes the joint to be in. + /// Gets the 'public' rotation of the joint. /// - LLQuaternion getRotationDelta() const { return mRotation.deltaRotation; } + LLQuaternion getPublicRotation() const { return mCurrentState.mRotation; } /// - /// Sets the rotation the animator wishes the joint to be in. + /// Sets the 'public' rotation of the joint. /// - void setRotationDelta(const LLQuaternion& rot); + /// + /// '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. + /// + void setPublicRotation(const LLQuaternion& rot); /// /// Reflects the base and delta rotation of the represented joint left-right. @@ -93,7 +98,7 @@ class FSJointPose void reflectRotation(); /// - /// Sets the base rotation of the represented joint to zero. + /// Sets the private rotation of the represented joint to zero. /// void zeroBaseRotation(); @@ -104,43 +109,20 @@ class FSJointPose bool isBaseRotationZero() const; /// - /// Undoes the last rotation set, if any. - /// Ordinarily the queue does not contain the current rotation, because we rely on time to add, and not button-up. - /// When we undo, if we are at the top of the queue, we need to add the current rotation so we can redo back to it. - /// Thus when we start undoing, mUndoneRotationIndex points at the current rotation. + /// Gets whether a redo of this joint may be performed. /// - void undoLastRotationChange(); + /// true if the joint may have a redo applied, otherwise false. + bool canPerformRedo() const { return mUndoneJointStatesIndex > 0; } /// - /// Redoes the last rotation set, if any. + /// Gets the 'public' scale of the joint. /// - void redoLastRotationChange(); + LLVector3 getPublicScale() const { return mCurrentState.mScale; } /// - /// Gets whether a redo of this joints rotation may be performed. + /// Sets the 'public' scale of the joint. /// - /// true if the joint can have a redo applied, otherwise false. - bool canRedoRotation() const { return mUndoneRotationIndex > 0; } - - /// - /// Gets the scale the animator wishes the joint to have. - /// - LLVector3 getScaleDelta() const { return mScaleDelta; } - - /// - /// Sets the scale the animator wishes the joint to have. - /// - void setScaleDelta(const LLVector3& scale); - - /// - /// Undoes the last scale set, if any. - /// - void undoLastScaleChange(); - - /// - /// Redoes the last scale set, if any. - /// - void redoLastScaleChange(); + void setPublicScale(const LLVector3& scale); /// /// Exchanges the rotations between two joints. @@ -161,6 +143,11 @@ class FSJointPose /// Resets the beginning properties of the joint this represents. /// void recaptureJoint(); + /// + /// Recalculates the delta reltive to the base for a new rotation. + /// + void recaptureJointAsDelta(); + /// /// Reverts the position/rotation/scale to their values when the animation begun. @@ -168,52 +155,90 @@ class FSJointPose /// void revertJoint(); - LLVector3 getTargetPosition() const { return mPositionDelta + mBeginningPosition; } - LLQuaternion getTargetRotation() const { return mRotation.getTargetRotation(); } - LLVector3 getTargetScale() const { return mScaleDelta + mBeginningScale; } + 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; } - /// - /// A class wrapping base and delta rotation, attempting to keep baseRotation as secret as possible. - /// Among other things, facilitates easy undo/redo through the joint-recapture process. - /// - class FSJointRotation + class FSJointState { public: - FSJointRotation(LLQuaternion base) { baseRotation.set(base); } - - FSJointRotation(LLQuaternion base, LLQuaternion delta) + FSJointState(LLJoint* joint) { - baseRotation.set(base); - deltaRotation.set(delta); + mBaseRotation.set(joint->getRotation()); + mBasePosition.set(joint->getPosition()); + mBaseScale.set(joint->getScale()); } - FSJointRotation() = default; - - LLQuaternion baseRotation; - LLQuaternion deltaRotation; - LLQuaternion getTargetRotation() const { return deltaRotation * baseRotation; } + FSJointState() = default; + LLQuaternion mDeltaRotation; + LLQuaternion getTargetRotation() const { return mRotation * mBaseRotation; } + LLVector3 getTargetPosition() const { return mPosition + mBasePosition; } + LLVector3 getTargetScale() const { return mScale + mBaseScale; } + void updateRotation(const LLQuaternion& newRotation) + { + auto inv_base = mBaseRotation; + inv_base.conjugate(); + mDeltaRotation = newRotation * inv_base; + }; + void reflectRotation() { - baseRotation.mQ[VX] *= -1; - baseRotation.mQ[VZ] *= -1; - deltaRotation.mQ[VX] *= -1; - deltaRotation.mQ[VZ] *= -1; + mBaseRotation.mQ[VX] *= -1; + mBaseRotation.mQ[VZ] *= -1; + mRotation.mQ[VX] *= -1; + mRotation.mQ[VZ] *= -1; } - void set(const FSJointRotation& jRot) + void cloneRotationFrom(FSJointState otherState) { - baseRotation.set(jRot.baseRotation); - deltaRotation.set(jRot.deltaRotation); + mBaseRotation.set(otherState.mBaseRotation); + mRotation.set(otherState.mRotation); } + + bool baseRotationIsZero() const { return mBaseRotation == LLQuaternion::DEFAULT; } + + void zeroBaseRotation() { mBaseRotation = LLQuaternion::DEFAULT; } + + void revertJointToBase(LLJoint* joint) const + { + if (!joint) + return; + + joint->setRotation(mBaseRotation); + joint->setPosition(mBasePosition); + joint->setScale(mBaseScale); + } + + private: + FSJointState(FSJointState* state) + { + mBaseRotation.set(state->mBaseRotation); + mBasePosition.set(state->mBasePosition); + mBaseScale.set(state->mBaseScale); + + mRotation.set(state->mRotation); + mPosition.set(state->mPosition); + mScale.set(state->mScale); + } + + public: + LLQuaternion mRotation; + LLVector3 mPosition; + LLVector3 mScale; + + private: + LLQuaternion mBaseRotation; + LLVector3 mBasePosition; + LLVector3 mBaseScale; }; -private: + private: std::string mJointName = ""; // expected to be a match to LLJoint.getName() for a joint implementation. LLPointer mJointState{ nullptr }; @@ -223,32 +248,15 @@ private: /// bool mIsCollisionVolume{ false }; - FSJointRotation mRotation; - std::deque mLastSetRotationDeltas; - size_t mUndoneRotationIndex = 0; - std::chrono::system_clock::time_point mTimeLastUpdatedRotation = std::chrono::system_clock::now(); + std::deque mLastSetJointStates; + size_t mUndoneJointStatesIndex = 0; + std::chrono::system_clock::time_point mTimeLastUpdatedCurrentState = std::chrono::system_clock::now(); - LLVector3 mPositionDelta; - LLVector3 mBeginningPosition; - std::deque mLastSetPositionDeltas; - size_t mUndonePositionIndex = 0; - std::chrono::system_clock::time_point mTimeLastUpdatedPosition = std::chrono::system_clock::now(); + FSJointState mCurrentState; - /// - /// Joint scales require special treatment, as they do not revert when we stop animating an avatar. - /// - LLVector3 mScaleDelta; - LLVector3 mBeginningScale; - std::deque mLastSetScaleDeltas; - size_t mUndoneScaleIndex = 0; - std::chrono::system_clock::time_point mTimeLastUpdatedScale = std::chrono::system_clock::now(); - - template - void addToUndo(T delta, size_t* undoIndex, std::deque* dequeue, std::chrono::system_clock::time_point* timeLastUpdated); - - template T undoLastChange(T thingToSet, size_t* undoIndex, std::deque* dequeue); - - template T redoLastChange(T thingToSet, size_t* undoIndex, std::deque* dequeue); + void addStateToUndo(FSJointState stateToAddToUndo); + FSJointState undoLastStateChange(FSJointState currentState); + FSJointState redoLastStateChange(FSJointState currentState); }; #endif // FS_JOINTPPOSE_H diff --git a/indra/newview/fsjointrotatetool.cpp b/indra/newview/fsjointrotatetool.cpp new file mode 100644 index 0000000000..c235ceeeb0 --- /dev/null +++ b/indra/newview/fsjointrotatetool.cpp @@ -0,0 +1,66 @@ +#include "fsjointrotatetool.h" +#include "llviewerobject.h" +#include "llvoavatar.h" +#include "llviewercontrol.h" + +FSJointRotateTool::FSJointRotateTool() : LLTool(std::string("Rotate Joint")) +{ +} + +void FSJointRotateTool::handleSelect() +{ + LL_INFOS() << "Rotate tool selected" << LL_ENDL; +} + +void FSJointRotateTool::handleDeselect() +{ + LL_INFOS() << "Rotate tool deselected" << LL_ENDL; +} + +bool FSJointRotateTool::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (findSelectedManipulator(x, y)) + { + LLVOAvatar* avatar = gAgentAvatarp; // Assume the avatar is the agent avatar + if (avatar) + { + rotateJoint(avatar); + } + } + return true; +} + +bool FSJointRotateTool::handleHover(S32 x, S32 y, MASK mask) +{ + return findSelectedManipulator(x, y); +} + +void FSJointRotateTool::render() +{ + computeManipulatorSize(); + renderManipulators(); +} + +void FSJointRotateTool::rotateJoint(LLVOAvatar* avatar) +{ + // Use joint rotation APIs, perhaps from FSPoserAnimator + LL_INFOS() << "Rotating joint" << LL_ENDL; +} + +bool FSJointRotateTool::findSelectedManipulator(S32 x, S32 y) +{ + // Implement logic to detect user selection of a manipulator + return false; +} + +void FSJointRotateTool::computeManipulatorSize() +{ + mManipulatorSize = 5.0f; // Example size +} + +void FSJointRotateTool::renderManipulators() +{ + // Render manipulators (axes or rotation handles) using OpenGL + gGL.color4f(0.5f, 0.5f, 0.5f, 0.5f); // Example color + gGL.flush(); +} diff --git a/indra/newview/fsjointrotatetool.h b/indra/newview/fsjointrotatetool.h new file mode 100644 index 0000000000..728c946b02 --- /dev/null +++ b/indra/newview/fsjointrotatetool.h @@ -0,0 +1,32 @@ +#ifndef FS_JOINTROTATETOOL_H +#define FS_JOINTROTATETOOL_H + +#include "lltool.h" +#include "llbbox.h" +#include "llvoavatar.h" + +class FSJointRotateTool : public LLTool, public LLSingleton +{ + LLSINGLETON(FSJointRotateTool); + +public: + void handleSelect() override; + void handleDeselect() override; + bool handleMouseDown(S32 x, S32 y, MASK mask) override; + bool handleHover(S32 x, S32 y, MASK mask) override; + void render() override; + +private: + void rotateJoint(LLVOAvatar* avatar); + bool findSelectedManipulator(S32 x, S32 y); + void computeManipulatorSize(); + void renderManipulators(); + + LLBBox mBBox; + F32 mManipulatorSize; + S32 mHighlightedAxis; + F32 mHighlightedDirection; + bool mForce; +}; + +#endif // FS_JOINTROTATETOOL_H diff --git a/indra/newview/fsmaniprotatejoint.cpp b/indra/newview/fsmaniprotatejoint.cpp new file mode 100644 index 0000000000..8e5c4466da --- /dev/null +++ b/indra/newview/fsmaniprotatejoint.cpp @@ -0,0 +1,1382 @@ +/** + * @file fsmaniproatejoint.cpp + * @brief custom manipulator for rotating joints + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (c) 2025 Beq Janus @ 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 + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + + #include "llviewerprecompiledheaders.h" + + #include "fsmaniprotatejoint.h" + +// library includes +#include "llmath.h" +#include "llgl.h" +#include "llrender.h" +#include "v4color.h" +#include "llprimitive.h" +#include "llview.h" +#include "llfontgl.h" + +#include "llrendersphere.h" +#include "llvoavatar.h" +#include "lljoint.h" +#include "llagent.h" // for gAgent, etc. +#include "llagentcamera.h" +#include "llappviewer.h" +#include "llcontrol.h" +#include "llfloaterreg.h" +#include "llresmgr.h" // for LLLocale +#include "llviewerwindow.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewershadermgr.h" +#include "fsfloaterposer.h" +// ------------------------------------- + + +/** + * @brief Renders a pulsing sphere at a specified joint position in the world. + * + * This function creates a visual indicator in the form of a pulsing sphere + * at a given joint position. The sphere's size oscillates over time to create + * a pulsing effect, making it easier to identify in the 3D space. + * + * @param joint_world_position The position of the joint in world coordinates where the sphere should be rendered. + * @param color The color of the sphere. Defaults to white (1.0, 1.0, 1.0, 1.0). + * + * @return void + * + */ +static void renderPulsingSphere(const LLVector3& joint_world_position, const LLColor4& color = LLColor4(1.f, 1.f, 1.f, 1.f)) +{ + constexpr float MAX_SPHERE_RADIUS = 0.05f; // Base radius in agent-space units. + constexpr float PULSE_AMPLITUDE = 0.01f; // Additional radius variation. + constexpr float PULSE_FREQUENCY = 1.f; // Pulses per second. + constexpr float PULSE_TIME_DOMAIN = 5.f; // Keep the time input small. + + // Get the current time (in seconds) from the global timer. + const U64 timeMicrosec = gFrameTime; + // Convert microseconds to seconds + const F64 timeSec = std::fmod(static_cast(timeMicrosec) / 1000000.0, PULSE_TIME_DOMAIN); + // Compute the pulse factor using a sine wave. This value oscillates between 0 and 1. + float pulseFactor = 0.75f + 0.25f * std::sin(PULSE_FREQUENCY * 2.f * F_PI * static_cast(timeSec)); + + // Calculate the current sphere radius. + float currentRadius = MAX_SPHERE_RADIUS - PULSE_AMPLITUDE * pulseFactor; + + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); + LLGLDepthTest gls_depth(GL_TRUE); + LLGLEnable gl_blend(GL_BLEND); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { + + // Translate to the joint's position + gGL.translatef(joint_world_position.mV[VX], joint_world_position.mV[VY], joint_world_position.mV[VZ]); + gGL.pushMatrix(); + { + gDebugProgram.bind(); + + LLGLEnable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_FALSE); + gGL.pushMatrix(); + { + LLColor4 color; + gGL.color4f(0.f, 0.f, 1.f, 0.3f); + gGL.diffuseColor4f(0.f, 0.f, 1.f, 0.3f); + + gGL.scalef(currentRadius, currentRadius, currentRadius); + + gSphere.render(); + gGL.flush(); + } + gGL.popMatrix(); + + gUIProgram.bind(); + } + gGL.popMatrix(); + } + gGL.popMatrix(); + + // Check for OpenGL errors + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) + { + LL_INFOS() << "OpenGL Error: " << err << LL_ENDL; + } +} + + +static bool isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera) +{ + LLViewerCamera* camera = LLViewerCamera::getInstance(); + + // Transform joint world position to screen coordinates + LLCoordGL jointScreenPos; + camera->projectPosAgentToScreen(jointWorldPos, jointScreenPos); + + // Get the world view rect + LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); + F32 half_width = (F32)world_view_rect.getWidth() / 2.f; + F32 half_height = (F32)world_view_rect.getHeight() / 2.f; + + // Convert mouse coordinates to be relative to the center of the screen + LLVector2 mousePos((F32)mouseX - half_width, (F32)mouseY - half_height); + + // Convert joint screen position to be relative to the center of the screen + LLVector2 joint2d(jointScreenPos.mX - half_width, jointScreenPos.mY - half_height); + + // Calculate the distance between mouse and joint in screen space + LLVector2 delta = joint2d - mousePos; + + // Calculate the distance from the camera to the joint + outDistanceFromCamera = (jointWorldPos - camera->getOrigin()).magVec(); + + // Calculate the apparent radius of the joint on the screen + F32 apparentRadius = jointRadius * camera->getPixelMeterRatio() / outDistanceFromCamera; + + // Check if the mouse is within the joint's radius + return (delta.magVecSquared() < apparentRadius * apparentRadius); +} + +//static +std::unordered_map FSManipRotateJoint::sReferenceUpVectors = {}; + +//static +const std::vector FSManipRotateJoint::sSelectableJoints = +{ + // head, torso, legs + { "mHead" }, + { "mNeck" }, + { "mPelvis" }, + { "mChest" }, + { "mTorso" }, + { "mCollarLeft" }, + { "mShoulderLeft" }, + { "mElbowLeft" }, + { "mWristLeft" }, + { "mCollarRight" }, + { "mShoulderRight" }, + { "mElbowRight" }, + { "mWristRight" }, + { "mHipLeft" }, + { "mKneeLeft" }, + { "mAnkleLeft" }, + { "mHipRight" }, + { "mKneeRight" }, + { "mAnkleRight" }, + +}; + +const std::unordered_map FSManipRotateJoint::sRingParams = +{ + { LL_ROT_Z, { LL_ROT_Z, LLVector4(1.f, 1.f, SELECTED_MANIPULATOR_SCALE, 1.f), 0.f, LLVector3(), LLColor4(0.f,0.f,1.f,1.f), LLColor4(0.f,0.f,1.f,0.3f), 2 } }, + { LL_ROT_Y, { LL_ROT_Y, LLVector4(1.f, SELECTED_MANIPULATOR_SCALE, 1.f, 1.f), 90.f, LLVector3(1.f,0.f,0.f), LLColor4(0.f,1.f,0.f,1.f), LLColor4(0.f,1.f,0.f,0.3f), 1 } }, + { LL_ROT_X, { LL_ROT_X, LLVector4(SELECTED_MANIPULATOR_SCALE, 1.f, 1.f, 1.f), 90.f, LLVector3(0.f,1.f,0.f), LLColor4(1.f,0.f,0.f,1.f), LLColor4(1.f,0.f,0.f,0.3f), 0 } } +}; +// Helper function: Builds an alignment quaternion from the computed bone axes. +// This quaternion rotates from the default coordinate system (assumed to be +// X = (1,0,0), Y = (0,1,0), Z = (0,0,1)) into the bone’s natural coordinate system. +LLQuaternion FSManipRotateJoint::computeAlignmentQuat(const BoneAxes& boneAxes) const +{ + LLQuaternion alignmentQuat(boneAxes.naturalX, boneAxes.naturalY, boneAxes.naturalZ); + alignmentQuat.normalize(); + return alignmentQuat; +} + +/** + * @brief Computes the natural axes for the bone associated with the joint. + * + * This function calculates a set of orthogonal axes that represent the natural + * orientation of the bone. It uses the joint's end point and a reference vector + * to determine these axes, with the Z-axis along the joint/bone and the Y axis + * perpendicular and in the plan of the world vertical, except when the joint is vertical + * in which case the X-axis is used. You can provide a custom reference vector by setting + * an entry in the sReferenceUpVectors map (joints like thumbs need this ideally). + * + * @return BoneAxes A struct containing the computed natural X, Y, and Z axes for the bone. + */ +FSManipRotateJoint::BoneAxes FSManipRotateJoint::computeBoneAxes() const +{ + BoneAxes axes; + + // Use 0,0,0 as local start for joint. + LLVector3 joint_local_pos (0.f,0.f,0.f); + + // Transform the local endpoint (mEnd) into world space. + LLVector3 localEnd = mJoint->getEnd(); + + axes.naturalZ = localEnd - joint_local_pos; + axes.naturalZ.normalize(); + + // Choose a reference vector. We'll use world up (0,1,0) as the default, + // but check for an override. + LLVector3 reference(0.f, 0.f, 1.f); + std::string jointName = mJoint->getName(); + auto iter = sReferenceUpVectors.find(jointName); + if (iter != sReferenceUpVectors.end()) + { + reference = iter->second; + } + + // However, if the bone is nearly vertical relative to world up, then world up may be almost co-linear with naturalZ. + if (std::fabs(axes.naturalZ * reference) > 0.99f) + { + // Use an alternate reference (+x) + reference = LLVector3(1.f, 0.f, 0.f); + } + + // Now, we want the naturalY to be the projection of the chosen reference onto the plane + // between natrualZ and rference. + // naturalY = reference - (naturalZ dot reference)*naturalZ (I think) + axes.naturalY = reference - (axes.naturalZ * (axes.naturalZ * reference)); + axes.naturalY.normalize(); + + // Compute naturalX as the cross product of naturalY and naturalZ. + axes.naturalX = axes.naturalY % axes.naturalZ; + axes.naturalX.normalize(); + + return axes; +} + +/** + * @brief Highlights the joint sphere that the mouse is hovering over. + * + * This function iterates through all "selectable joints" of the avatar and checks if the mouse + * is hovering over any of them. It updates the highlighted joint to be the closest one to the camera + * that the mouse is over. + * the Selectable Joints are currently statically defined as a usable subset of all joints. + * TODO(Beq) allow other subsets to be highlighted/selected when editing specifica areas such as face or hands. + * + * @param mouseX The x-coordinate of the mouse cursor in screen space. + * @param mouseY The y-coordinate of the mouse cursor in screen space. + * + * @return void + * + * @note This function updates the mHighlightedJoint and mHighlightedPartDistance member variables. + */ +void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY) +{ + // Ensure we have an avatar to work with. + if (!mAvatar) return; + mHighlightedJoint = nullptr; // reset the highlighted joint + + // Iterate through the avatar's joint map. + for (const auto& entry : getSelectableJoints()) + { + LLJoint* joint = mAvatar->getJoint(std::string(entry)); + if (!joint) + continue; + + // Update the joint's world matrix to ensure its position is current. + joint->updateWorldMatrixParent(); + joint->updateWorldMatrix(); + + // Retrieve the joint's world position (in agent space). + LLVector3 jointWorldPos = joint->getWorldPosition(); + LLCachedControl target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.2f); + F32 distance_from_camera; + if (isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera) == true) + { + // we want to highlight the closest + if (!mHighlightedJoint || mHighlightedPartDistance > distance_from_camera) + { + mHighlightedJoint = joint; + mHighlightedPartDistance = distance_from_camera; + } + } + } +} + +FSManipRotateJoint::FSManipRotateJoint(LLToolComposite* composite) +: LLManipRotate(composite) +{} + +// ------------------------------------- + +void FSManipRotateJoint::setJoint(LLJoint* joint) +{ + mJoint = joint; + + // Save initial rotation as baseline for delta rotation + if (mJoint) + { + mSavedJointRot = mJoint->getWorldRotation(); + mBoneAxes = computeBoneAxes(); + mNaturalAlignmentQuat = computeAlignmentQuat(mBoneAxes); + } +} + +void FSManipRotateJoint::setAvatar(LLVOAvatar* avatar) +{ + mAvatar = avatar; +} + +/** + * @brief Handles the selection of the joint rotate tool + * + * This function is called when the rotate tool is selectedfor manipulation. + * + * @note It serves no purpose right now but might be useful once we add transform and scale + */ +void FSManipRotateJoint::handleSelect() +{ + // Not entirely sure this is needed in the current implementation. + if (mJoint) + { + mSavedJointRot = mJoint->getWorldRotation(); + } +} + +// We override this because we don't have a selection center from LLSelectMgr. +// Mostly copied from the base class, but without the selection center logic. +// Instead, we get the joint's position, convert to global, and store in mRotationCenter. +/** + * @brief Updates the visibility and positioning of the joint manipulator. + * + * This function calculates whether the joint manipulator should be visible + * and updates its position and scale based on the camera view and UI scale. + * It also determines if the camera is edge-on to the manipulator's axis. + * The "edge on" state is not used currently but is used in the base class for stepping + * + * @return bool Returns true if the manipulator is visible, false otherwise. + */ +bool FSManipRotateJoint::updateVisiblity() +{ + if (!mJoint) + { + // No joint to manipulate, not visible + return false; + } + + if (!hasMouseCapture()) + { + mRotationCenter = gAgent.getPosGlobalFromAgent( mJoint->getWorldPosition() ); + } + + bool visible = false; + + //Assume that UI scale factor is equivalent for X and Y axis + F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; + + const LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal( mRotationCenter ); // Convert from world/agent to global + + const auto * viewer_camera = LLViewerCamera::getInstance(); + visible = viewer_camera->projectPosAgentToScreen(agent_space_center, mCenterScreen ); + if (visible) + { + mCenterToCam = gAgentCamera.getCameraPositionAgent() - agent_space_center; + mCenterToCamNorm = mCenterToCam; + mCenterToCamMag = mCenterToCamNorm.normalize(); + LLVector3 cameraAtAxis = viewer_camera->getAtAxis(); + cameraAtAxis.normalize(); + + F32 z_dist = -1.f * (mCenterToCam * cameraAtAxis); + + // Don't drag manip if object too far away + if (mCenterToCamMag > 0.001f) + { + F32 fraction_of_fov = RADIUS_PIXELS / static_cast(viewer_camera->getViewHeightInPixels()); + F32 apparent_angle = fraction_of_fov * viewer_camera->getView(); // radians + mRadiusMeters = z_dist * tan(apparent_angle); + mRadiusMeters *= ui_scale_factor; + + mCenterToProfilePlaneMag = mRadiusMeters * mRadiusMeters / mCenterToCamMag; + mCenterToProfilePlane = -mCenterToProfilePlaneMag * mCenterToCamNorm; + } + else + { + visible = false; + } + } + + mCamEdgeOn = false; + F32 axis_onto_cam = mManipPart >= LL_ROT_X ? llabs( getConstraintAxis() * mCenterToCamNorm ) : 0.f; + if (axis_onto_cam < AXIS_ONTO_CAM_TOLERANCE) + { + mCamEdgeOn = true; + } + + return visible; +} + + +/** + * @brief Updates the scale of a specific manipulator part. + * + * This function smoothly interpolates the scale of a given manipulator part + * towards its target scale. It uses a predefined set of ring parameters and + * applies smooth interpolation for visual consistency. + * + * @param part The manipulator part to update (e.g., LL_ROT_X, LL_ROT_Y, LL_ROT_Z). + * @param scales Reference to the LLVector4 containing current scales, which will be updated. + * + * @note This function modifies the 'scales' parameter in-place. + */ +void FSManipRotateJoint::updateManipulatorScale(EManipPart part, LLVector4& scales) +{ + auto iter = sRingParams.find(part); + if (iter != sRingParams.end()) + { + scales = lerp(scales, iter->second.targetScale, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + } +} + +// Render a single ring using the given parameters and pass index (for multi-pass rendering). +void FSManipRotateJoint::renderRingPass(const RingRenderParams& params, float radius, float width, int pass) +{ + gGL.pushMatrix(); + { + // If an extra rotation is specified, apply it. + if (params.extraRotateAngle != 0.f) + { + gGL.rotatef(params.extraRotateAngle, params.extraRotateAxis.mV[0], + params.extraRotateAxis.mV[1], params.extraRotateAxis.mV[2]); + } + // Get the appropriate scale value from mManipulatorScales. + float scaleVal = 1.f; + switch (params.scaleIndex) + { + case 0: scaleVal = mManipulatorScales.mV[VX]; break; + case 1: scaleVal = mManipulatorScales.mV[VY]; break; + case 2: scaleVal = mManipulatorScales.mV[VZ]; break; + case 3: scaleVal = mManipulatorScales.mV[VW]; break; + default: break; + } + gGL.scalef(scaleVal, scaleVal, scaleVal); + gl_ring(radius, width, params.primaryColor, params.secondaryColor, CIRCLE_STEPS, pass); + } + gGL.popMatrix(); +} + +/** + * @brief Renders the manipulator rings for joint rotation. + * + * This function renders the manipulator rings used for rotating joints. It handles + * the rendering of the center sphere and individual rings based on the current + * manipulation state and highlighted parts. + * + * @param agent_space_center The center point of the manipulator in agent space. + * @param rotation The rotation to be applied to the manipulator rings. + * + * @return void + */ +void FSManipRotateJoint::renderManipulatorRings(const LLVector3& agent_space_center, const LLQuaternion& rotation) +{ + F32 width_meters = WIDTH_PIXELS * mRadiusMeters / RADIUS_PIXELS; + // Translate to the joint's position + const auto joint_world_position = mJoint->getWorldPosition(); + gDebugProgram.bind(); + gGL.pushMatrix(); + { + LLGLEnable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_FALSE); + LLGLEnable clip_plane0(GL_CLIP_PLANE0); + gGL.translatef(joint_world_position.mV[VX], joint_world_position.mV[VY], joint_world_position.mV[VZ]); + + LLMatrix4 rot_mat(rotation); + gGL.multMatrix((GLfloat*)rot_mat.mMatrix); + + for (int pass = 0; pass < 2; ++pass) + { + if( mManipPart == LL_NO_PART || mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) + { + renderCenterSphere( mRadiusMeters); + for (auto& ring_params : sRingParams) + { + const auto part = ring_params.first; + const auto& params = ring_params.second; + if (mHighlightedPart == part) + { + updateManipulatorScale(part, mManipulatorScales); + } + renderRingPass(params, mRadiusMeters, width_meters, pass); + } + if( mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) + { + static const auto roll_color = LLColor4(1.f,0.65f,0.0f,0.4f); + updateManipulatorScale(mManipPart, mManipulatorScales); + gGL.pushMatrix(); + { + // Cancel the rotation applied earlier: + LLMatrix4 inv_rot_mat(rotation); + inv_rot_mat.invert(); + gGL.multMatrix((GLfloat*)inv_rot_mat.mMatrix); + renderCenterCircle( mRadiusMeters*1.2f, roll_color, roll_color ); + } + gGL.popMatrix(); + } + } + else + { + auto iter = sRingParams.find(mManipPart); + if (iter != sRingParams.end()) + { + updateManipulatorScale(mManipPart, mManipulatorScales); + renderRingPass(iter->second, mRadiusMeters, width_meters, pass); + } + } + } + } + gGL.popMatrix(); + gUIProgram.bind(); +} + +void FSManipRotateJoint::renderCenterCircle(const F32 radius, const LLColor4& normal_color, const LLColor4& highlight_color) +{ + gGL.pushMatrix(); + { + LLGLEnable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_FALSE); + + constexpr int segments = 64; + glLineWidth(6.0f); // Set the desired line thickness + + // Compute a scale factor that already factors in the radius. + float scale = radius; + scale *= (mManipulatorScales.mV[VX] + mManipulatorScales.mV[VY] + + mManipulatorScales.mV[VZ] + mManipulatorScales.mV[VW]) / 4.0f; + gGL.diffuseColor4fv(normal_color.mV); + gGL.scalef(scale, scale, scale); + + // Rotate the unit circle so its normal (0,0,1) aligns with mCenterToCamNorm. + LLVector3 defaultNormal(0.f, 0.f, 1.f); + LLVector3 targetNormal = mCenterToCamNorm; + targetNormal.normalize(); // Ensure it is normalized. + + LLVector3 rotationAxis = defaultNormal % targetNormal; // Cross product. + F32 dot = defaultNormal * targetNormal; // Dot product. + F32 angle = acosf(dot) * RAD_TO_DEG; // Convert to degrees. + + if (rotationAxis.magVec() > 0.001f) + { + gGL.rotatef(angle, rotationAxis.mV[VX], rotationAxis.mV[VY], rotationAxis.mV[VZ]); + } + + // Draw a unit circle in the XY plane (which is now rotated correctly). + gGL.begin(LLRender::LINE_LOOP); + for (int i = 0; i < segments; i++) + { + float theta = 2.0f * 3.14159f * i / segments; + // Use a unit circle here. + LLVector3 offset(cosf(theta), sinf(theta), 0.f); + gGL.vertex3fv(offset.mV); + } + gGL.end(); + + glLineWidth(1.0f); // Reset the line width. + } + gGL.popMatrix(); +} + +void FSManipRotateJoint::renderCenterSphere(const F32 radius, const LLColor4& normal_color, const LLColor4& highlight_color) +{ + gGL.pushMatrix(); + { + LLGLEnable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_FALSE); + + float scale = radius * 0.8f; + + if (mManipPart == LL_ROT_GENERAL || mHighlightedPart == LL_ROT_GENERAL) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, SELECTED_MANIPULATOR_SCALE), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.diffuseColor4fv(highlight_color.mV); + scale *= mManipulatorScales.mV[VW]; + } + else + { + // no part selected, just a semi transp white sphere + gGL.diffuseColor4fv(normal_color.mV); + // Use an average of the manipulator scales when no specific part is selected + scale *= (mManipulatorScales.mV[VX] + mManipulatorScales.mV[VY] + mManipulatorScales.mV[VZ] + mManipulatorScales.mV[VW]) / 4.0f; + } + + gGL.scalef(scale, scale, scale); + gSphere.render(); + + gGL.flush(); + } + gGL.popMatrix(); +} + + +/** + * @brief Renders the joint rotation manipulator and associated visual elements. + * + * This function is responsible for rendering the joint rotation manipulator, + * including the manipulator rings, axes, and debug information. It handles + * the visibility checks, GL state setup, and calls to specific rendering + * functions for different components of the manipulator. + * + * The function performs the following main tasks: + * 1. Checks for the presence of a valid joint and avatar. + * 2. Updates the visibility and rotation center. + * 3. Sets up the GL state for rendering. + * 4. Renders a pulsing sphere for highlighted joints (if applicable). + * 5. Updates joint world matrices. + * 6. Computes the active rotation based on user settings. + * 7. Renders the manipulator axes and rings. + * 8. Displays debug information (Euler angles, including delta of the active drag). + * + * This function does not take any parameters and does not return a value. + * It operates on the internal state of the FSManipRotateJoint object. + */ +void FSManipRotateJoint::render() +{ + // Early-out if no joint or avatar. + if (!mJoint || !mAvatar) + { + return; + } + + // update visibility and rotation center. + if (!updateVisiblity()) + { + return; + } + // Setup GL state. + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); + LLGLDepthTest gls_depth(GL_TRUE); + LLGLEnable gl_blend(GL_BLEND); + + // Optionally, if another joint is highlighted, render a pulsing sphere. + if (mHighlightedJoint && mJoint != mHighlightedJoint) + { + mHighlightedJoint->updateWorldMatrixParent(); + mHighlightedJoint->updateWorldMatrix(); + renderPulsingSphere(mHighlightedJoint->getWorldPosition()); + } + + // Update joint world matrices. + mJoint->updateWorldMatrixParent(); + mJoint->updateWorldMatrix(); + + const LLQuaternion joint_world_rotation = mJoint->getWorldRotation(); + + const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT; + + LLQuaternion currentLocalRot = mJoint->getRotation(); + + LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot; + rotatedNaturalAlignment.normalize(); + // Compute the final world alignment: + LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot; + final_world_alignment.normalize(); + + const LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + + LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); + LLQuaternion active_rotation = use_natural_direction? final_world_alignment : joint_world_rotation; + // Render the manipulator rings in a separate function. + gGL.matrixMode(LLRender::MM_MODELVIEW); + renderAxes(agent_space_center, mRadiusMeters * 1.5f, active_rotation); + renderManipulatorRings(agent_space_center, active_rotation); + + // Debug: render joint's Euler angles for diagnostic purposes. + LLVector3 euler_angles; + active_rotation.getEulerAngles(&euler_angles.mV[0], + &euler_angles.mV[1], + &euler_angles.mV[2]); + euler_angles *= RAD_TO_DEG; + euler_angles.mV[0] = ll_round(fmodf(euler_angles.mV[0] + 360.f, 360.f), 0.05f); + euler_angles.mV[1] = ll_round(fmodf(euler_angles.mV[1] + 360.f, 360.f), 0.05f); + euler_angles.mV[2] = ll_round(fmodf(euler_angles.mV[2] + 360.f, 360.f), 0.05f); + renderNameXYZ(euler_angles); +} + +void FSManipRotateJoint::renderAxes(const LLVector3& agent_space_center, F32 size, const LLQuaternion& rotation) +{ + LLGLEnable cull_face(GL_CULL_FACE); + LLGLEnable clip_plane0(GL_CLIP_PLANE0); + LLGLDepthTest gls_depth(GL_FALSE); + gGL.pushMatrix(); + gGL.translatef(agent_space_center.mV[VX], agent_space_center.mV[VY], agent_space_center.mV[VZ]); + + LLMatrix4 rot_mat(rotation); + + gGL.multMatrix((GLfloat*)rot_mat.mMatrix); + + gGL.begin(LLRender::LINES); + + // X-axis (Red) + gGL.color4f(1.0f, 0.0f, 0.0f, 1.0f); + gGL.vertex3f(-size, 0.0f, 0.0f); + gGL.vertex3f(size, 0.0f, 0.0f); + + // Y-axis (Green) + gGL.color4f(0.0f, 1.0f, 0.0f, 1.0f); + gGL.vertex3f(0.0f, -size, 0.0f); + gGL.vertex3f(0.0f, size, 0.0f); + + // Z-axis (Blue) + gGL.color4f(0.0f, 0.0f, 1.0f, 1.0f); + gGL.vertex3f(0.0f, 0.0f, -size); + gGL.vertex3f(0.0f, 0.0f, size); + + gGL.end(); + + gGL.popMatrix(); +} + +//static +std::string FSManipRotateJoint::getManipPartString(EManipPart part) +{ + switch (part) + { + case LL_NO_PART: return "None"; + case LL_X_ARROW: return "X Arrow"; + case LL_Y_ARROW: return "Y Arrow"; + case LL_Z_ARROW: return "Z Arrow"; + case LL_YZ_PLANE: return "YZ Plane"; + case LL_XZ_PLANE: return "XZ Plane"; + case LL_XY_PLANE: return "XY Plane"; + case LL_CORNER_NNN: return "Corner ---"; + case LL_CORNER_NNP: return "Corner --+"; + case LL_CORNER_NPN: return "Corner -+-"; + case LL_CORNER_NPP: return "Corner -++"; + case LL_CORNER_PNN: return "Corner +--"; + case LL_CORNER_PNP: return "Corner +-+"; + case LL_CORNER_PPN: return "Corner ++-"; + case LL_CORNER_PPP: return "Corner +++"; + case LL_FACE_POSZ: return "Face +Z"; + case LL_FACE_POSX: return "Face +X"; + case LL_FACE_POSY: return "Face +Y"; + case LL_FACE_NEGX: return "Face -X"; + case LL_FACE_NEGY: return "Face -Y"; + case LL_FACE_NEGZ: return "Face -Z"; + case LL_EDGE_NEGX_NEGY: return "Edge -X-Y"; + case LL_EDGE_NEGX_POSY: return "Edge -X+Y"; + case LL_EDGE_POSX_NEGY: return "Edge +X-Y"; + case LL_EDGE_POSX_POSY: return "Edge +X+Y"; + case LL_EDGE_NEGY_NEGZ: return "Edge -Y-Z"; + case LL_EDGE_NEGY_POSZ: return "Edge -Y+Z"; + case LL_EDGE_POSY_NEGZ: return "Edge +Y-Z"; + case LL_EDGE_POSY_POSZ: return "Edge +Y+Z"; + case LL_EDGE_NEGZ_NEGX: return "Edge -Z-X"; + case LL_EDGE_NEGZ_POSX: return "Edge -Z+X"; + case LL_EDGE_POSZ_NEGX: return "Edge +Z-X"; + case LL_EDGE_POSZ_POSX: return "Edge +Z+X"; + case LL_ROT_GENERAL: return "Rotate General"; + case LL_ROT_X: return "Rotate X"; + case LL_ROT_Y: return "Rotate Y"; + case LL_ROT_Z: return "Rotate Z"; + case LL_ROT_ROLL: return "Rotate Roll"; + default: return "Unknown"; + } +} + +/** + * @brief Renders the XYZ coordinates and additional information as text overlay on the screen. + * + * This function displays the X, Y, and Z coordinates of the given vector, along with the delta angle, + * joint name, and manipulation part. It creates a semi-transparent background and renders the text + * with shadow effects for better visibility. + * + * @param vec The LLVector3 containing the X, Y, and Z coordinates to be displayed. + * + * @return void + * + * @note This function assumes the existence of class member variables such as mLastAngle, mJoint, and mManipPart. + * It also uses global functions and objects like gViewerWindow, LLUI, and LLFontGL. + */ +void FSManipRotateJoint::renderNameXYZ(const LLVector3 &vec) +{ + constexpr S32 PAD = 10; + S32 window_center_x = gViewerWindow->getWorldViewRectScaled().getWidth() / 2; + S32 window_center_y = gViewerWindow->getWorldViewRectScaled().getHeight() / 2; + S32 vertical_offset = window_center_y - VERTICAL_OFFSET; + + gGL.pushMatrix(); + { + LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); + gViewerWindow->setup2DRender(); + const LLVector2& display_scale = gViewerWindow->getDisplayScale(); + gGL.color4f(0.f, 0.f, 0.f, 0.7f); + + imagep->draw( + (S32)((window_center_x - 150) * display_scale.mV[VX]), + (S32)((window_center_y + vertical_offset - PAD) * display_scale.mV[VY]), + (S32)(340 * display_scale.mV[VX]), + (S32)((PAD * 2 + 10) * display_scale.mV[VY] * 2), + LLColor4(0.f, 0.f, 0.f, 0.7f) + ); + + LLFontGL* font = LLFontGL::getFontSansSerif(); + LLLocale locale(LLLocale::USER_LOCALE); + LLGLDepthTest gls_depth(GL_FALSE); + + auto renderTextWithShadow = [&](const std::string& text, F32 x, F32 y, const LLColor4& color) { + font->render(utf8str_to_wstring(text), 0, x + 1.f, y - 2.f, LLColor4::black, + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, nullptr); + font->render(utf8str_to_wstring(text), 0, x, y, color, + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, nullptr); + }; + + F32 base_y = (F32)(window_center_y + vertical_offset); + renderTextWithShadow(llformat("X: %.3f", vec.mV[VX]), window_center_x - 122.f, base_y, LLColor4(1.f, 0.5f, 0.5f, 1.f)); + renderTextWithShadow(llformat("Y: %.3f", vec.mV[VY]), window_center_x - 47.f, base_y, LLColor4(0.5f, 1.f, 0.5f, 1.f)); + renderTextWithShadow(llformat("Z: %.3f", vec.mV[VZ]), window_center_x + 28.f, base_y, LLColor4(0.5f, 0.5f, 1.f, 1.f)); + renderTextWithShadow(llformat("Δ: %.3f", mLastAngle * RAD_TO_DEG), window_center_x + 103.f, base_y, LLColor4(1.f, 0.65f, 0.f, 1.f)); + base_y += 20.f; + renderTextWithShadow(llformat("Joint: %s", mJoint->getName().c_str()), window_center_x - 130.f, base_y, LLColor4(1.f, 0.1f, 1.f, 1.f)); + renderTextWithShadow(llformat("Manip: %s", getManipPartString(mManipPart).c_str()), window_center_x + 30.f, base_y, LLColor4(1.f, 1.f, .1f, 1.f)); + } + gGL.popMatrix(); + + gViewerWindow->setup3DRender(); +} + +void FSManipRotateJoint::renderActiveRing( F32 radius, F32 width, const LLColor4& front_color, const LLColor4& back_color) +{ + LLGLEnable cull_face(GL_CULL_FACE); + { + gl_ring(radius, width, back_color, back_color * 0.5f, CIRCLE_STEPS, false); + gl_ring(radius, width, back_color, back_color * 0.5f, CIRCLE_STEPS, true); + } + { + LLGLDepthTest gls_depth(GL_FALSE); + gl_ring(radius, width, front_color, front_color * 0.5f, CIRCLE_STEPS, false); + gl_ring(radius, width, front_color, front_color * 0.5f, CIRCLE_STEPS, true); + } +} + + +// ------------------------------------- +// Overriding because the base uses mObjectSelection->getFirstMoveableObject(true) +// Not sure we use it though...TBC (see mouse down on part instead) +bool FSManipRotateJoint::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (!mJoint) + { + return false; + } + + // Highlight the manipulator as before. + highlightManipulators(x, y); + + if (mHighlightedPart != LL_NO_PART) + { + mManipPart = (EManipPart)mHighlightedPart; + + // Get the joint's center in agent space. + LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + + // Use the existing function to get the intersection point. + LLVector3 intersection = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters); + + // Check if the returned intersection is valid. + if (intersection.isExactlyZero()) + { + // Treat this as a "raycast miss" and do not capture the mouse. + return false; + } + else + { + // Save the valid intersection point. + mInitialIntersection = intersection; + // Also store the joint's current rotation. + mSavedJointRot = mJoint->getWorldRotation(); + + // Capture the mouse for dragging. + setMouseCapture(true); + return true; + } + } + return false; +} + +/** + * @brief Handles the mouse down event on a manipulator part. + * + * Along with render, this is the main top-level entry. + * This function determines which manipulator part (ring/axis) is under the mouse cursor + * using the highlightManipulator() function and highlights the selectable joints. + * It then saves the joint's current world rotation as the basis for the drag operation + * and sets the appropriate manipulation part. + * Depending on the manipulation part, it either performs an unconstrained rotation + * or a constrained rotation based on the axis. + * + * @param x The x-coordinate of the mouse cursor. + * @param y The y-coordinate of the mouse cursor. + * @param mask The mask indicating the state of modifier keys. + * @return true if the mouse down event is handled successfully, false otherwise. + */ +bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask) +{ + auto * poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); + // Determine which ring (axis) is under the mouse, also highlights selectable joints. + highlightManipulators(x, y); + // For joint manipulation, require both a valid joint and avatar. + if (!mJoint || !mAvatar || !poser) + { + return false; + } + poser->setFocus(true); + S32 hit_part = mHighlightedPart; + + // Save the joint’s current world rotation as the basis for the drag. + mSavedJointRot = mJoint->getWorldRotation(); + + mManipPart = (EManipPart)hit_part; + + // Convert rotation center from global to agent space. + LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + + // based on mManipPArt (set in highlightmanipulators). decide whether we are constrained or not in the rotation + if (mManipPart == LL_ROT_GENERAL) + { + // Unconstrained rotation. we use the intersection point as the mouse down point. + mMouseDown = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters); + mInitialIntersection = mMouseDown; // Save the initial sphere intersection. + } + else + { + // Constrained rotation. + LLVector3 axis = setConstraintAxis(); // set the axis based on the manipulator part + F32 axis_onto_cam = llabs(axis * mCenterToCamNorm); + const F32 AXIS_ONTO_CAM_TOL = cos(85.f * DEG_TO_RAD); + if (axis_onto_cam < AXIS_ONTO_CAM_TOL) + { + LLVector3 up_from_axis = mCenterToCamNorm % axis; + up_from_axis.normalize(); + LLVector3 cur_intersection; + getMousePointOnPlaneAgent(cur_intersection, x, y, agent_space_center, mCenterToCam); + cur_intersection -= agent_space_center; + mMouseDown = projected_vec(cur_intersection, up_from_axis); + F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; + F32 mouse_dist_sqrd = mMouseDown.magVecSquared(); + if (mouse_dist_sqrd > 0.0001f) + { + mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * + (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - mouse_dist_sqrd); + } + LLVector3 projected_center_to_cam = mCenterToCamNorm - projected_vec(mCenterToCamNorm, axis); + mMouseDown += mouse_depth * projected_center_to_cam; + } + else + { + mMouseDown = findNearestPointOnRing(x, y, agent_space_center, axis) - agent_space_center; + mMouseDown.normalize(); + } + mInitialIntersection = mMouseDown; + } + + // Set the current mouse vector equal to the initial one. + mMouseCur = mMouseDown; + + // Save the agent’s “at” axis (this might be used later in drag calculations). + mAgentSelfAtAxis = gAgent.getAtAxis(); + + // Capture the mouse so that subsequent mouse drag events are routed here. + setMouseCapture(true); + + // (Optionally, reset any help text timer or related UI feedback.) + mHelpTextTimer.reset(); + sNumTimesHelpTextShown++; + + return true; +} + +// We use mouseUp to update the UI, updating it during the drag is too slow. +bool FSManipRotateJoint::handleMouseUp(S32 x, S32 y, MASK mask) +{ + + auto * poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); + if (hasMouseCapture()) + { + // Update the UI, by causing it to read back the position of the selected joints and aply those relative to the base rot + if (poser) + { + poser->updatePosedBones(); + } + + // Release mouse + setMouseCapture(false); + mManipPart = LL_NO_PART; + mLastAngle = 0.0f; + return true; + } + else if(mHighlightedJoint) + { + if (poser) + { + poser->selectJointByName(mHighlightedJoint->getName()); + } + return true; + } + return false; +} + + +/** + * @brief Does all the hard work of working out what inworld control we are interacting with + * + * There's quite a bit of overlap with the base class. + * Sadly the base is built around object selection, so we need to override. + * We also take this opportunity to highlight nearby joints that we might want to manipulate. + * + * @param x mouse x-coordinate + * @param y mouse y-coordinate + */ +void FSManipRotateJoint::highlightManipulators(S32 x, S32 y) +{ + // Clear any previous highlight. + mHighlightedPart = LL_NO_PART; + // Instead of using mObjectSelection->getFirstMoveableObject(), + // simply require that the joint (and the avatar) is valid. + if (!mJoint || !mAvatar) + { + highlightHoverSpheres(x, y); + gViewerWindow->setCursor(UI_CURSOR_ARROW); + return; + } + + // Decide which rotation to use based on a user toggle. + LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); + // Compute the rotation center in agent space. + LLVector3 agent_space_rotation_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + + // Update joint world matrices. + mJoint->updateWorldMatrixParent(); + mJoint->updateWorldMatrix(); + + const LLQuaternion joint_world_rotation = mJoint->getWorldRotation(); + + const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT; + + LLQuaternion currentLocalRot = mJoint->getRotation(); + + LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot; + rotatedNaturalAlignment.normalize(); + // Compute the final world alignment: + LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot; + final_world_alignment.normalize(); + + + LLQuaternion joint_rot = use_natural_direction ? final_world_alignment : joint_world_rotation; + + // Compute the three local axes in world space. + LLVector3 rot_x_axis = LLVector3::x_axis * joint_rot; + LLVector3 rot_y_axis = LLVector3::y_axis * joint_rot; + LLVector3 rot_z_axis = LLVector3::z_axis * joint_rot; + + // mCenterToCamNorm is assumed to be computed already (for example in updateVisibility) + F32 proj_rot_x_axis = llabs(rot_x_axis * mCenterToCamNorm); + F32 proj_rot_y_axis = llabs(rot_y_axis * mCenterToCamNorm); + F32 proj_rot_z_axis = llabs(rot_z_axis * mCenterToCamNorm); + + // Variables to help choose the best candidate. + F32 min_select_distance = 0.f; + F32 cur_select_distance = 0.f; + + // These vectors will hold the intersection points on planes defined by each axis. + LLVector3 mouse_dir_x, mouse_dir_y, mouse_dir_z, intersection_roll; + + // For each axis, compute the mouse intersection on a plane passing through the rotation center. + getMousePointOnPlaneAgent(mouse_dir_x, x, y, agent_space_rotation_center, rot_x_axis); + mouse_dir_x -= agent_space_rotation_center; + mouse_dir_x *= 1.f + (1.f - llabs(rot_x_axis * mCenterToCamNorm)) * 0.1f; + + getMousePointOnPlaneAgent(mouse_dir_y, x, y, agent_space_rotation_center, rot_y_axis); + mouse_dir_y -= agent_space_rotation_center; + mouse_dir_y *= 1.f + (1.f - llabs(rot_y_axis * mCenterToCamNorm)) * 0.1f; + + getMousePointOnPlaneAgent(mouse_dir_z, x, y, agent_space_rotation_center, rot_z_axis); + mouse_dir_z -= agent_space_rotation_center; + mouse_dir_z *= 1.f + (1.f - llabs(rot_z_axis * mCenterToCamNorm)) * 0.1f; + + // For roll, intersect with a plane defined by the camera’s direction. + getMousePointOnPlaneAgent(intersection_roll, x, y, agent_space_rotation_center, mCenterToCamNorm); + intersection_roll -= agent_space_rotation_center; + + // Compute the distances (in agent-space) from the rotation center. + F32 dist_x = mouse_dir_x.normalize(); + F32 dist_y = mouse_dir_y.normalize(); + F32 dist_z = mouse_dir_z.normalize(); + + // Compute a threshold for selection. + F32 distance_threshold = (MAX_MANIP_SELECT_DISTANCE * mRadiusMeters) / gViewerWindow->getWorldViewHeightScaled(); + + // Define a lambda to test an axis ring. This captures variables by reference. + auto testAxisRing = [&](F32 dist, const LLVector3& mouse_dir, F32 proj_factor, LLManip::e_manip_part ringId) { + F32 absDiff = llabs(dist - mRadiusMeters); + // Instead of multiplying by proj_factor, we divide the threshold by it, + // so that near edge-on views (small proj_factor) yield a larger tolerance. + if (absDiff < distance_threshold / llmax(0.05f, proj_factor)) + { + F32 cur_select_distance = dist * (mouse_dir * mCenterToCamNorm); + if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) + { + min_select_distance = cur_select_distance; + mHighlightedPart = ringId; + } + } + }; + + // Use the lambda for each axis. + testAxisRing(dist_x, mouse_dir_x, proj_rot_x_axis, LL_ROT_X); + testAxisRing(dist_y, mouse_dir_y, proj_rot_y_axis, LL_ROT_Y); + testAxisRing(dist_z, mouse_dir_z, proj_rot_z_axis, LL_ROT_Z); + + // --- Additional tests for edge-on intersections --- + if (proj_rot_x_axis < 0.05f) + { + if ((proj_rot_y_axis > 0.05f && (dist_y * llabs(mouse_dir_y * rot_x_axis) < distance_threshold) && dist_y < mRadiusMeters) || + (proj_rot_z_axis > 0.05f && (dist_z * llabs(mouse_dir_z * rot_x_axis) < distance_threshold) && dist_z < mRadiusMeters)) + { + mHighlightedPart = LL_ROT_X; + } + } + if (proj_rot_y_axis < 0.05f) + { + if ((proj_rot_x_axis > 0.05f && (dist_x * llabs(mouse_dir_x * rot_y_axis) < distance_threshold) && dist_x < mRadiusMeters) || + (proj_rot_z_axis > 0.05f && (dist_z * llabs(mouse_dir_z * rot_y_axis) < distance_threshold) && dist_z < mRadiusMeters)) + { + mHighlightedPart = LL_ROT_Y; + } + } + if (proj_rot_z_axis < 0.05f) + { + if ((proj_rot_x_axis > 0.05f && (dist_x * llabs(mouse_dir_x * rot_z_axis) < distance_threshold) && dist_x < mRadiusMeters) || + (proj_rot_y_axis > 0.05f && (dist_y * llabs(mouse_dir_y * rot_z_axis) < distance_threshold) && dist_y < mRadiusMeters)) + { + mHighlightedPart = LL_ROT_Z; + } + } + + // --- Test for roll if no primary axis was highlighted --- + if (mHighlightedPart == LL_NO_PART) + { + F32 roll_distance = intersection_roll.magVec(); + F32 width_meters = WIDTH_PIXELS * mRadiusMeters / RADIUS_PIXELS; + + if (llabs(roll_distance - (mRadiusMeters + (width_meters * 2.f))) < distance_threshold * 2.f) + { + mHighlightedPart = LL_ROT_ROLL; + } + else if (roll_distance < mRadiusMeters) + { + mHighlightedPart = LL_ROT_GENERAL; + } + } + + // If nothing else of interest then test for nearby joints we can select. + if (mHighlightedPart == LL_NO_PART) + { + highlightHoverSpheres(x, y); + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_TOOLROTATE); + } +} + + +// ------------------------------------- +bool FSManipRotateJoint::handleHover(S32 x, S32 y, MASK mask) +{ + // If we are dragging (hasMouseCapture), + // we do the "drag" logic but apply rotation to the joint + if (hasMouseCapture() && mJoint) + { + drag(x, y); // calls dragConstrained() or dragUnconstrained() + // but in drag(), we must override it so the final rotation + // is applied to the joint instead of an LLViewerObject + gViewerWindow->setCursor(UI_CURSOR_TOOLROTATE); + } + else + { + highlightManipulators(x, y); + } + return true; +} + +LLQuaternion FSManipRotateJoint::dragUnconstrained(S32 x, S32 y) +{ + // Get the camera position and the joint’s pivot (in agent space) + LLVector3 cam = gAgentCamera.getCameraPositionAgent(); + LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + + // Compute the current intersection on the sphere. + mMouseCur = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters); + + // Use the screen center (set in updateVisibility) to compute how far + // the mouse is from the sphere’s center in screen space. + F32 delta_x = (F32)(mCenterScreen.mX - x); + F32 delta_y = (F32)(mCenterScreen.mY - y); + F32 dist_from_sphere_center = sqrtf(delta_x * delta_x + delta_y * delta_y); + + // Compute a rotation axis from the stored initial intersection to the current intersection. + LLVector3 axis = mInitialIntersection % mMouseCur; + F32 angle = atan2f(sqrtf(axis * axis), mInitialIntersection * mMouseCur); + axis.normalize(); + LLQuaternion sphere_rot(angle, axis); + + // If there is negligible change, return the identity. + if (is_approx_zero(1.f - mInitialIntersection * mMouseCur)) + { + return LLQuaternion::DEFAULT; + } + // If the mouse is still near the center of the manipulator in screen space, + // simply return the computed sphere rotation. + else if (dist_from_sphere_center < RADIUS_PIXELS) + { + return sphere_rot; + } + else + { + // Otherwise, compute an “extra” rotation based on a projection onto a profile plane. + LLVector3 intersection; + // Use the previously computed mCenterToProfilePlane and mCenterToCamNorm. + // This computes a point on the plane defined by (center + mCenterToProfilePlane) and oriented by mCenterToCamNorm. + getMousePointOnPlaneAgent(intersection, x, y, agent_space_center + mCenterToProfilePlane, mCenterToCamNorm); + + // Determine the “in-sphere” angle that corresponds to dragging from centre to periphery. + F32 in_sphere_angle = F_PI_BY_TWO; + F32 dist_to_tangent_point = mRadiusMeters; + if (!is_approx_zero(mCenterToProfilePlaneMag)) + { + dist_to_tangent_point = sqrtf(mRadiusMeters * mRadiusMeters - mCenterToProfilePlaneMag * mCenterToProfilePlaneMag); + in_sphere_angle = atan2f(dist_to_tangent_point, mCenterToProfilePlaneMag); + } + + LLVector3 profile_center_to_intersection = intersection - (agent_space_center + mCenterToProfilePlane); + F32 dist_to_intersection = profile_center_to_intersection.normalize(); + F32 extra_angle = (-1.f + dist_to_intersection / dist_to_tangent_point) * in_sphere_angle; + + // Compute a rotation axis from the camera-to-center vector and the profile difference. + axis = (cam - agent_space_center) % profile_center_to_intersection; + axis.normalize(); + + // Multiply the unconstrained sphere rotation with the extra rotation. + return sphere_rot * LLQuaternion(extra_angle, axis); + } +} +LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y) +{ + // Get the constraint axis from our joint manipulator. + // (See the adjusted getConstraintAxis() below.) + LLVector3 constraint_axis = getConstraintAxis(); + LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + + // Project the current mouse position onto the plane defined by the constraint axis. + LLVector3 projected_mouse; + bool hit = getMousePointOnPlaneAgent(projected_mouse, x, y, agent_space_center, constraint_axis); + if (!hit) + { + return LLQuaternion::DEFAULT; + } + projected_mouse -= agent_space_center; + projected_mouse.normalize(); + + // Similarly, project the initial intersection (stored at mouse down) onto the same plane. + LLVector3 initial_proj = mInitialIntersection; + initial_proj -= (initial_proj * constraint_axis) * constraint_axis; + initial_proj.normalize(); + + // Compute the signed angle using atan2. + // The numerator is the magnitude of the cross product projected along the constraint axis. + float numerator = (initial_proj % projected_mouse) * constraint_axis; + // The denominator is the dot product. + float denominator = initial_proj * projected_mouse; + float angle = atan2(numerator, denominator); // angle in (-pi, pi) + mLastAngle = angle; + return LLQuaternion(angle, constraint_axis); +} + +void FSManipRotateJoint::drag(S32 x, S32 y) +{ + if (!updateVisiblity() || !mJoint) return; + + LLQuaternion delta_rot; + if (mManipPart == LL_ROT_GENERAL) + { + delta_rot = dragUnconstrained(x, y); + } + else + { + delta_rot = dragConstrained(x, y); + } + + // Compose the saved joint rotation with the delta to compute the new world rotation. + LLQuaternion new_world_rot = mSavedJointRot * delta_rot; + mJoint->setWorldRotation(new_world_rot); +} + +// set mConstrainedAxis based on mManipParat and returns it too. +LLVector3 FSManipRotateJoint::setConstraintAxis() +{ + LLVector3 axis; + if (mManipPart == LL_ROT_ROLL) + { + axis = mCenterToCamNorm; + } + else + { + // For constrained rotations about X, Y, or Z: + // Assume mManipPart is defined such that LL_ROT_X, LL_ROT_Y, LL_ROT_Z correspond to 0, 1, 2. + S32 axis_dir = mManipPart - LL_ROT_X; + axis.setZero(); + if (axis_dir >= LL_NO_PART && axis_dir < LL_Z_ARROW) + { + axis.mV[axis_dir] = 1.f; + } + else + { + axis.mV[0] = 1.f; // Fallback to X. + } + // Transform the local axis into world space using the joint's world rotation. + if (mJoint) + { + LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); + LLQuaternion active_rotation; + if (use_natural_direction) + { + // Get the joint's current local rotation. + LLQuaternion currentLocalRot = mJoint->getRotation(); + const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT; + LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot; + rotatedNaturalAlignment.normalize(); + LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot; + final_world_alignment.normalize(); + active_rotation = final_world_alignment; + } + else + { + active_rotation = mJoint->getWorldRotation(); + } + axis = axis * active_rotation; + axis.normalize(); + } + } + mConstraintAxis = axis; + return axis; +} diff --git a/indra/newview/fsmaniprotatejoint.h b/indra/newview/fsmaniprotatejoint.h new file mode 100644 index 0000000000..4feb5e2600 --- /dev/null +++ b/indra/newview/fsmaniprotatejoint.h @@ -0,0 +1,144 @@ +/** + * @file fsmaniproatejoint.h + * @brief custom manipulator for rotating joints + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (c) 2025 Beq Janus @ 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 + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#ifndef FS_MANIP_ROTATE_JOINT_H +#define FS_MANIP_ROTATE_JOINT_H + +#include "llselectmgr.h" +#include "llmaniprotate.h" + +class LLJoint; +class LLVOAvatar; // or LLVOAvatarSelf, etc. + +namespace { + const F32 AXIS_ONTO_CAM_TOLERANCE = cos( 80.f * DEG_TO_RAD ); // cos() is not constexpr til c++26 + constexpr F32 RADIUS_PIXELS = 100.f; // size in screen space + constexpr S32 CIRCLE_STEPS = 100; + constexpr F32 CIRCLE_STEP_SIZE = 2.0f * F_PI / CIRCLE_STEPS; + constexpr F32 SQ_RADIUS = RADIUS_PIXELS * RADIUS_PIXELS; + constexpr F32 WIDTH_PIXELS = 8; + constexpr F32 MAX_MANIP_SELECT_DISTANCE = 100.f; + constexpr F32 SNAP_ANGLE_INCREMENT = 5.625f; + constexpr F32 SNAP_ANGLE_DETENTE = SNAP_ANGLE_INCREMENT; + constexpr F32 SNAP_GUIDE_RADIUS_1 = 2.8f; + constexpr F32 SNAP_GUIDE_RADIUS_2 = 2.4f; + constexpr F32 SNAP_GUIDE_RADIUS_3 = 2.2f; + constexpr F32 SNAP_GUIDE_RADIUS_4 = 2.1f; + constexpr F32 SNAP_GUIDE_RADIUS_5 = 2.05f; + constexpr F32 SNAP_GUIDE_INNER_RADIUS = 2.f; + constexpr F32 SELECTED_MANIPULATOR_SCALE = 1.05f; + constexpr F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; + constexpr S32 VERTICAL_OFFSET = 100; +} + +class FSManipRotateJoint : public LLManipRotate +{ + // Used for overriding the natural "up" direction of a joint. + // if no override is set then the world up direction is used unless the joint is vertical in which case we pick an arbitrary normal + static std::unordered_map sReferenceUpVectors; + + struct BoneAxes + { + LLVector3 naturalX; + LLVector3 naturalY; + LLVector3 naturalZ; + }; + LLQuaternion computeAlignmentQuat( const BoneAxes& boneAxes ) const; + BoneAxes computeBoneAxes() const; + +public: + FSManipRotateJoint(LLToolComposite* composite); + virtual ~FSManipRotateJoint() {} + static std::string getManipPartString(EManipPart part); + // Called to designate which joint we are going to manipulate. + void setJoint(LLJoint* joint); + + void setAvatar(LLVOAvatar* avatar); + + // Overrides + void handleSelect() override; + bool updateVisiblity(); + void render() override; + void renderNameXYZ(const LLVector3 &vec); + bool handleMouseDown(S32 x, S32 y, MASK mask) override; + bool handleMouseUp(S32 x, S32 y, MASK mask) override; + bool handleHover(S32 x, S32 y, MASK mask) override; + void drag(S32 x, S32 y) override; + bool isAlwaysRendered() override { return true; } + void highlightManipulators(S32 x, S32 y) override; + bool handleMouseDownOnPart(S32 x, S32 y, MASK mask) override; + void highlightHoverSpheres(S32 mouseX, S32 mouseY); + +protected: + // void renderNameXYZ(const std::string name, const LLVector3 &vec); + LLQuaternion dragUnconstrained( S32 x, S32 y ); + LLQuaternion dragConstrained( S32 x, S32 y ); + LLVector3 getConstraintAxis() const { return mConstraintAxis; }; + LLVector3 setConstraintAxis(); + + // Instead of selecting an LLViewerObject, we have a single joint + LLJoint* mJoint = nullptr; + BoneAxes mBoneAxes; + LLQuaternion mNaturalAlignmentQuat; + LLVOAvatar* mAvatar = nullptr; + // We'll store the joint's original rotation for reference + LLQuaternion mSavedJointRot; + LLJoint * mHighlightedJoint = nullptr; + F32 mHighlightedPartDistance = 0.f; + LLVector3 mInitialIntersection; // The initial point on the manipulator’s sphere (in agent space) + const std::vector getSelectableJoints(){ return sSelectableJoints; }; + +private: + static const std::vector sSelectableJoints; + + // Structure holding parameters needed to render one manipulator ring. + struct RingRenderParams + { + EManipPart part; // e.g. LL_ROT_Z, LL_ROT_Y, LL_ROT_X, etc. + LLVector4 targetScale; // Target scale for mManipulatorScales for this part. + float extraRotateAngle; // Extra rotation angle (in degrees) to apply. + LLVector3 extraRotateAxis; // Axis for the extra rotation. + LLColor4 primaryColor; // Primary ring color. + LLColor4 secondaryColor; // Secondary ring color. + int scaleIndex; // Which component of mManipulatorScales to use (0: X, 1: Y, 2: Z, 3: Roll). + }; + + static const std::unordered_map sRingParams; + void updateManipulatorScale(EManipPart part, LLVector4& scales); + void renderActiveRing( F32 radius, F32 width, const LLColor4& front_color, const LLColor4& back_color); + void renderManipulatorRings(const LLVector3& center, const LLQuaternion& finalAlignment); + + void renderCenterCircle(const F32 radius, const LLColor4& normal_color = LLColor4(0.7f,0.7,0.7f,0.2), const LLColor4& highlight_color = LLColor4(0.8f,0.8f,0.8f,0.3)); + void renderCenterSphere(const F32 radius, const LLColor4& normal_color = LLColor4(0.7f,0.7,0.7f,0.2), const LLColor4& highlight_color = LLColor4(0.8f,0.8f,0.8f,0.3)); + void renderRingPass(const RingRenderParams& params, float radius, float width, int pass); + void renderAxes(const LLVector3& center, F32 size, const LLQuaternion& rotation); + + float mLastAngle = 0.f; + LLVector3 mConstraintAxis; +}; + +#endif // FS_MANIP_ROTATE_JOINT_H diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index 6845e8419c..39938d4490 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -78,7 +78,7 @@ void FSPoserAnimator::setPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoin posingMotion->removeJointFromState(jointPose); } -void FSPoserAnimator::resetAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint) +void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) { if (!isAvatarSafeToUse(avatar)) return; @@ -94,27 +94,7 @@ void FSPoserAnimator::resetAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& j if (!jointPose) return; - jointPose->setPositionDelta(LLVector3()); - jointPose->setRotationDelta(LLQuaternion()); -} - -void FSPoserAnimator::undoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) -{ - if (!isAvatarSafeToUse(avatar)) - return; - - FSPosingMotion* posingMotion = getPosingMotion(avatar); - if (!posingMotion) - return; - - if (posingMotion->isStopped()) - return; - - FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); - if (!jointPose) - return; - - jointPose->undoLastRotationChange(); + jointPose->undoLastChange(); if (style == NONE || style == DELTAMODE) return; @@ -123,10 +103,10 @@ void FSPoserAnimator::undoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoi if (!oppositeJointPose) return; - oppositeJointPose->undoLastRotationChange(); + oppositeJointPose->undoLastChange(); } -void FSPoserAnimator::undoLastJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) +void FSPoserAnimator::resetJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) { if (!isAvatarSafeToUse(avatar)) return; @@ -142,7 +122,9 @@ void FSPoserAnimator::undoLastJointPosition(LLVOAvatar* avatar, const FSPoserJoi if (!jointPose) return; - jointPose->undoLastPositionChange(); + jointPose->setPublicRotation(LLQuaternion()); + jointPose->setPublicPosition(LLVector3()); + jointPose->setPublicScale(LLVector3()); if (style == NONE || style == DELTAMODE) return; @@ -151,94 +133,12 @@ void FSPoserAnimator::undoLastJointPosition(LLVOAvatar* avatar, const FSPoserJoi if (!oppositeJointPose) return; - oppositeJointPose->undoLastPositionChange(); + oppositeJointPose->setPublicRotation(LLQuaternion()); + oppositeJointPose->setPublicPosition(LLVector3()); + oppositeJointPose->setPublicScale(LLVector3()); } -void FSPoserAnimator::undoLastJointScale(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) -{ - if (!isAvatarSafeToUse(avatar)) - return; - - FSPosingMotion* posingMotion = getPosingMotion(avatar); - if (!posingMotion) - return; - - if (posingMotion->isStopped()) - return; - - FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); - if (!jointPose) - return; - - jointPose->undoLastScaleChange(); - - if (style == NONE || style == DELTAMODE) - return; - - FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); - if (!oppositeJointPose) - return; - - oppositeJointPose->undoLastScaleChange(); -} - -void FSPoserAnimator::resetJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) -{ - if (!isAvatarSafeToUse(avatar)) - return; - - FSPosingMotion* posingMotion = getPosingMotion(avatar); - if (!posingMotion) - return; - - if (posingMotion->isStopped()) - return; - - FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); - if (!jointPose) - return; - - jointPose->setPositionDelta(LLVector3()); - - if (style == NONE || style == DELTAMODE) - return; - - FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); - if (!oppositeJointPose) - return; - - oppositeJointPose->setPositionDelta(LLVector3()); -} - -void FSPoserAnimator::resetJointScale(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) -{ - if (!isAvatarSafeToUse(avatar)) - return; - - FSPosingMotion* posingMotion = getPosingMotion(avatar); - if (!posingMotion) - return; - - if (posingMotion->isStopped()) - return; - - FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); - if (!jointPose) - return; - - jointPose->setScaleDelta(LLVector3()); - - if (style == NONE || style == DELTAMODE) - return; - - FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); - if (!oppositeJointPose) - return; - - oppositeJointPose->setScaleDelta(LLVector3()); -} - -bool FSPoserAnimator::canRedoJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint) +bool FSPoserAnimator::canRedoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint) { if (!isAvatarSafeToUse(avatar)) return false; @@ -254,10 +154,10 @@ bool FSPoserAnimator::canRedoJointRotation(LLVOAvatar* avatar, const FSPoserJoin if (!jointPose) return false; - return jointPose->canRedoRotation(); + return jointPose->canPerformRedo(); } -void FSPoserAnimator::redoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) +void FSPoserAnimator::redoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) { if (!isAvatarSafeToUse(avatar)) return; @@ -273,7 +173,7 @@ void FSPoserAnimator::redoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoi if (!jointPose) return; - jointPose->redoLastRotationChange(); + jointPose->redoLastChange(); if (style == NONE || style == DELTAMODE) return; @@ -282,63 +182,7 @@ void FSPoserAnimator::redoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoi if (!oppositeJointPose) return; - oppositeJointPose->redoLastRotationChange(); -} - -void FSPoserAnimator::redoLastJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) -{ - if (!isAvatarSafeToUse(avatar)) - return; - - FSPosingMotion* posingMotion = getPosingMotion(avatar); - if (!posingMotion) - return; - - if (posingMotion->isStopped()) - return; - - FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); - if (!jointPose) - return; - - jointPose->redoLastPositionChange(); - - if (style == NONE || style == DELTAMODE) - return; - - FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); - if (!oppositeJointPose) - return; - - oppositeJointPose->redoLastPositionChange(); -} - -void FSPoserAnimator::redoLastJointScale(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style) -{ - if (!isAvatarSafeToUse(avatar)) - return; - - FSPosingMotion* posingMotion = getPosingMotion(avatar); - if (!posingMotion) - return; - - if (posingMotion->isStopped()) - return; - - FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); - if (!jointPose) - return; - - jointPose->redoLastScaleChange(); - - if (style == NONE || style == DELTAMODE) - return; - - FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); - if (!oppositeJointPose) - return; - - oppositeJointPose->redoLastScaleChange(); + oppositeJointPose->redoLastChange(); } LLVector3 FSPoserAnimator::getJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint) const @@ -355,7 +199,7 @@ LLVector3 FSPoserAnimator::getJointPosition(LLVOAvatar* avatar, const FSPoserJoi if (!jointPose) return pos; - return jointPose->getPositionDelta(); + return jointPose->getPublicPosition(); } void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_BoneDeflectionStyles style) @@ -377,7 +221,7 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j if (!jointPose) return; - LLVector3 positionDelta = jointPose->getPositionDelta() - position; + LLVector3 positionDelta = jointPose->getPublicPosition() - position; switch (style) { @@ -385,13 +229,13 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j case MIRROR_DELTA: case SYMPATHETIC_DELTA: case SYMPATHETIC: - jointPose->setPositionDelta(position); + jointPose->setPublicPosition(position); break; case DELTAMODE: case NONE: default: - jointPose->setPositionDelta(position); + jointPose->setPublicPosition(position); return; } @@ -399,18 +243,18 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j if (!oppositeJointPose) return; - LLVector3 oppositeJointPosition = oppositeJointPose->getPositionDelta(); + LLVector3 oppositeJointPosition = oppositeJointPose->getPublicPosition(); switch (style) { case MIRROR: case MIRROR_DELTA: - oppositeJointPose->setPositionDelta(oppositeJointPosition + positionDelta); + oppositeJointPose->setPublicPosition(oppositeJointPosition + positionDelta); break; case SYMPATHETIC_DELTA: case SYMPATHETIC: - oppositeJointPose->setPositionDelta(oppositeJointPosition - positionDelta); + oppositeJointPose->setPublicPosition(oppositeJointPosition - positionDelta); break; default: @@ -478,6 +322,22 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi jointPose->recaptureJoint(); setPosingAvatarJoint(avatar, joint, true); } +void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) +{ + if (!isAvatarSafeToUse(avatar)) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); + if (!jointPose) + return; + + jointPose->recaptureJointAsDelta(); + setPosingAvatarJoint(avatar, joint, true); +} LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) const { @@ -493,7 +353,7 @@ LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoi if (!jointPose) return vec3; - return translateRotationFromQuaternion(translation, negation, jointPose->getRotationDelta()); + return translateRotationFromQuaternion(translation, negation, jointPose->getPublicRotation()); } void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation, @@ -523,27 +383,27 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j case SYMPATHETIC: case MIRROR: if (rotationStyle == DELTAIC_ROT) - jointPose->setRotationDelta(deltaRot * jointPose->getRotationDelta()); + jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation()); else - jointPose->setRotationDelta(absRot); + jointPose->setPublicRotation(absRot); break; case SYMPATHETIC_DELTA: case MIRROR_DELTA: - jointPose->setRotationDelta(deltaRot * jointPose->getRotationDelta()); + jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation()); break; case DELTAMODE: - jointPose->setRotationDelta(deltaRot * jointPose->getRotationDelta()); + jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation()); return; case NONE: default: if (rotationStyle == DELTAIC_ROT) - jointPose->setRotationDelta(deltaRot * jointPose->getRotationDelta()); + jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation()); else - jointPose->setRotationDelta(absRot); + jointPose->setPublicRotation(absRot); return; } @@ -560,7 +420,7 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j break; case SYMPATHETIC_DELTA: - oppositeJointPose->setRotationDelta(deltaRot * oppositeJointPose->getRotationDelta()); + oppositeJointPose->setPublicRotation(deltaRot * oppositeJointPose->getPublicRotation()); break; case MIRROR: @@ -569,7 +429,7 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j case MIRROR_DELTA: inv_quat = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]); - oppositeJointPose->setRotationDelta(inv_quat * oppositeJointPose->getRotationDelta()); + oppositeJointPose->setPublicRotation(inv_quat * oppositeJointPose->getPublicRotation()); break; default: @@ -603,6 +463,45 @@ void FSPoserAnimator::reflectJoint(LLVOAvatar* avatar, const FSPoserJoint* joint } } +void FSPoserAnimator::symmetrizeLeftToRightOrRightToLeft(LLVOAvatar* avatar, bool rightToLeft) +{ + if (!isAvatarSafeToUse(avatar)) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + for (size_t index = 0; index != PoserJoints.size(); ++index) + { + if (!PoserJoints[index].dontFlipOnMirror()) + continue; + + bool currentlyPosing = isPosingAvatarJoint(avatar, PoserJoints[index]); + if (!currentlyPosing) + continue; + + auto oppositeJoint = getPoserJointByName(PoserJoints[index].mirrorJointName()); + if (!oppositeJoint) + continue; + + bool currentlyPosingOppositeJoint = isPosingAvatarJoint(avatar, *oppositeJoint); + if (!currentlyPosingOppositeJoint) + continue; + + FSJointPose* rightJointPose = posingMotion->getJointPoseByJointName(PoserJoints[index].jointName()); + FSJointPose* leftJointPose = posingMotion->getJointPoseByJointName(oppositeJoint->jointName()); + + if (!leftJointPose || !rightJointPose) + return; + + if (rightToLeft) + leftJointPose->mirrorRotationFrom(rightJointPose); + else + rightJointPose->mirrorRotationFrom(leftJointPose); + } +} + void FSPoserAnimator::flipEntirePose(LLVOAvatar* avatar) { if (!isAvatarSafeToUse(avatar)) @@ -750,7 +649,7 @@ LLVector3 FSPoserAnimator::getJointScale(LLVOAvatar* avatar, const FSPoserJoint& if (!jointPose) return scale; - return jointPose->getScaleDelta(); + return jointPose->getPublicScale(); } void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_BoneDeflectionStyles style) @@ -772,7 +671,7 @@ void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* join if (!jointPose) return; - jointPose->setScaleDelta(scale); + jointPose->setPublicScale(scale); FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName()); if (!oppositeJointPose) return; @@ -783,7 +682,7 @@ void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* join case MIRROR: case SYMPATHETIC_DELTA: case MIRROR_DELTA: - oppositeJointPose->setScaleDelta(scale); + oppositeJointPose->setPublicScale(scale); break; case DELTAMODE: @@ -810,10 +709,10 @@ bool FSPoserAnimator::tryGetJointSaveVectors(LLVOAvatar* avatar, const FSPoserJo if (!jointPose) return false; - LLQuaternion rotationDelta = jointPose->getRotationDelta(); + LLQuaternion rotationDelta = jointPose->getPublicRotation(); rotationDelta.getEulerAngles(&rot->mV[VX], &rot->mV[VY], &rot->mV[VZ]); - pos->set(jointPose->getPositionDelta()); - scale->set(jointPose->getScaleDelta()); + pos->set(jointPose->getPublicPosition()); + scale->set(jointPose->getPublicScale()); *baseRotationIsZero = jointPose->isBaseRotationZero(); return true; @@ -836,7 +735,7 @@ void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint* jointPose->zeroBaseRotation(); LLQuaternion rot = translateRotationToQuaternion(SWAP_NOTHING, NEGATE_NOTHING, rotation); - jointPose->setRotationDelta(rot); + jointPose->setPublicRotation(rot); } void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position) @@ -853,9 +752,9 @@ void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* return; if (loadPositionAsDelta) - jointPose->setPositionDelta(position); + jointPose->setPublicPosition(position); else - jointPose->setPositionDelta(position); + jointPose->setPublicPosition(position); } void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale) @@ -872,9 +771,9 @@ void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joi return; if (loadScaleAsDelta) - jointPose->setScaleDelta(scale); + jointPose->setPublicScale(scale); else - jointPose->setScaleDelta(scale); + jointPose->setPublicScale(scale); } const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 7f7ce6e334..045558288a 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -398,46 +398,19 @@ public: void setPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, bool shouldPose); /// - /// Resets the supplied PoserJoint to its position/rotation/scale it was when poser was started. - /// - /// The avatar having the joint to which we refer. - /// The joint to be reset. - void resetAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint); - - /// - /// Undoes the last applied rotation to the supplied PoserJoint. - /// - /// The avatar having the joint to which we refer. - /// The joint with the rotation to undo. - void undoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); - - /// - /// Undoes the last applied position to the supplied PoserJoint. - /// - /// The avatar having the joint to which we refer. - /// The joint with the position to undo. - void undoLastJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); - - /// - /// Undoes the last applied scale to the supplied PoserJoint. - /// - /// The avatar having the joint to which we refer. - /// The joint with the scale to undo. - void undoLastJointScale(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); - - /// - /// Resets the position of the supplied PoserJoint. + /// Resets the supplied PoserJoint to the position it had when poser was started. /// /// The avatar having the joint to which we refer. /// The joint with the position to reset. - void resetJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); + /// The style to apply the reset with; if a style that support more than one joint, more that one joint will be reset. + void resetJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); /// - /// Resets the scale of the supplied joint to initial values. + /// Undoes the last applied change (rotation, position or scale) to the supplied PoserJoint. /// /// The avatar having the joint to which we refer. - /// The joint with the scale to reset. - void resetJointScale(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); + /// The joint with the rotation to undo. + void undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); /// /// Determines if a redo action is currently permitted for the supplied joint. @@ -445,28 +418,14 @@ public: /// The avatar having the joint to which we refer. /// The joint to query. /// True if a redo action is available, otherwise false. - bool canRedoJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint); + bool canRedoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint); /// - /// Re-does the last undone rotation to the supplied PoserJoint. + /// Re-does the last undone change (rotation, position or scale) to the supplied PoserJoint. /// /// The avatar having the joint to which we refer. /// The joint with the rotation to redo. - void redoLastJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); - - /// - /// Re-does the last undone position to the supplied PoserJoint. - /// - /// The avatar having the joint to which we refer. - /// The joint with the position to redo. - void redoLastJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); - - /// - /// Re-does the last undone scale to the supplied PoserJoint. - /// - /// The avatar having the joint to which we refer. - /// The joint with the scale to redo. - void redoLastJointScale(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); + void redoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style); /// /// Gets the position of a joint for the supplied avatar. @@ -541,14 +500,23 @@ public: /// The avatar whose pose should flip left-right. void flipEntirePose(LLVOAvatar* avatar); + /// + /// Symmetrizes the rotations of the joints from one side of the supplied avatar to the other. + /// + /// The avatar whose joints to symmetrizet. + /// Whether to symmetrize rotations from right to left, otherwise symmetrize left to right. + void symmetrizeLeftToRightOrRightToLeft(LLVOAvatar* avatar, bool rightToLeft); + /// /// Recaptures the rotation, position and scale state of the supplied joint for the supplied avatar. + /// AsDelta variant retians the original base and creates a delta relative to it. /// /// The avatar whose joint is to be recaptured. /// The joint to recapture. /// The axial translation form the supplied joint. /// The style of negation to apply to the recapture. void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation); + void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation); /// /// Sets all of the joint rotations of the supplied avatar to zero. diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index fab282552c..fc418dd911 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -260,7 +260,7 @@ void FSPosingMotion::setAllRotationsToZero() for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) { poserJoint_iter->zeroBaseRotation(); - poserJoint_iter->setRotationDelta(LLQuaternion::DEFAULT); + poserJoint_iter->setPublicRotation(LLQuaternion::DEFAULT); } } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index c639f6319d..4542e90dfa 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -763,7 +763,7 @@ LLAppViewer::LLAppViewer() // static_debug_info.log file std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); // Improve Bugsplat tracking by using attributes - BugSplatAttributes::setCrashContextFileName(logdir + "crash-context.xml"); + BugSplatAttributes::setCrashContextFileName(logdir + "CrashContext.xml"); // # else // ! LL_BUGSPLAT // write Google Breakpad minidump files to a per-run dump directory to avoid multiple viewer issues. diff --git a/indra/newview/llmaniprotate.h b/indra/newview/llmaniprotate.h index fa764a9ced..c0c83c305a 100644 --- a/indra/newview/llmaniprotate.h +++ b/indra/newview/llmaniprotate.h @@ -40,6 +40,7 @@ class LLColor4; class LLManipRotate : public LLManip { + friend class FSManipRotateJoint; public: class ManipulatorHandle { @@ -67,13 +68,13 @@ public: private: void updateHoverView(); - void drag( S32 x, S32 y ); + virtual void drag( S32 x, S32 y ); LLVector3 projectToSphere( F32 x, F32 y, bool* on_sphere ); void renderSnapGuides(); void renderActiveRing(F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color); - bool updateVisiblity(); + virtual bool updateVisiblity(); LLVector3 findNearestPointOnRing( S32 x, S32 y, const LLVector3& center, const LLVector3& axis ); LLQuaternion dragUnconstrained( S32 x, S32 y ); diff --git a/indra/newview/lltoolcomp.cpp b/indra/newview/lltoolcomp.cpp index e393a84b3c..c4d3fbe167 100644 --- a/indra/newview/lltoolcomp.cpp +++ b/indra/newview/lltoolcomp.cpp @@ -899,3 +899,165 @@ bool LLToolCompGun::handleScrollWheel(S32 x, S32 y, S32 clicks) } return true; } + + +#include "llviewerwindow.h" // for gViewerWindow->pickAsync() +#include "llselectmgr.h" // for LLSelectMgr +#include "llfloaterreg.h" // for LLFloaterReg::showInstance() +#include "llviewermenu.h" // for LLEditMenuHandler::gEditMenuHandler +#include "fsfloaterposer.h" + +// If you want a standard static instance approach: +FSToolCompPose* FSToolCompPose::getInstance() +{ + // Meyers singleton pattern + static FSToolCompPose instance; + return &instance; +} + +//----------------------------------- +// Constructor +FSToolCompPose::FSToolCompPose() +: LLToolComposite(std::string("Pose")) +{ + // Create a joint manipulator + mManip = new FSManipRotateJoint(this); + + // Possibly create a selection rectangle tool if you want + // to be able to box-select joints or objects + // (same usage as LLToolCompRotate does) + // mSelectRect = new LLToolSelectRect(this); + + // Set the default and current subtool + mCur = mManip; + mDefault = mManip; +} + +//----------------------------------- +// Destructor +FSToolCompPose::~FSToolCompPose() +{ + delete mManip; + mManip = nullptr; + + delete mSelectRect; + mSelectRect = nullptr; +} + +//----------------------------------- +// Handle Hover +bool FSToolCompPose::handleHover(S32 x, S32 y, MASK mask) +{ + // If the current subtool hasn't captured the mouse, + // switch to your manip subtool (like LLToolCompRotate). + if (!mCur->hasMouseCapture()) + { + setCurrentTool(mManip); + } + return mCur->handleHover(x, y, mask); +} + +//----------------------------------- +// Handle MouseDown +bool FSToolCompPose::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDown = true; + + // Kick off an async pick, which calls pickCallback when complete + // so we can see if user clicked on a manip ring or not + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +//----------------------------------- +// The pickCallback +void FSToolCompPose::pickCallback(const LLPickInfo& pick_info) +{ + FSToolCompPose* self = FSToolCompPose::getInstance(); + FSManipRotateJoint* manip = self->mManip; + + if (!manip) return; // No manipulator available, exit + + // Highlight the manipulator based on the mouse position + manip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); + + if (!self->mMouseDown) + { + // No action needed if mouse is up; interaction is handled by highlight logic + return; + } + + // Check if a manipulator ring is highlighted + if (manip->getHighlightedPart() != LLManip::LL_NO_PART) + { + // Switch to the manipulator tool for dragging + self->setCurrentTool(manip); + manip->handleMouseDownOnPart(pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask); + } + else + { + // If no ring is highlighted, reset interaction or do nothing + LL_DEBUGS("FSToolCompPose") << "No manipulator ring selected" << LL_ENDL; + } +} + + +//----------------------------------- +// Handle MouseUp +bool FSToolCompPose::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mMouseDown = false; + // The base LLToolComposite sets mCur->handleMouseUp(...) + // and does other management + return LLToolComposite::handleMouseUp(x, y, mask); +} + +//----------------------------------- +// getOverrideTool +// If you want SHIFT+CTRL combos to do something else +LLTool* FSToolCompPose::getOverrideTool(MASK mask) +{ + // Example from LLToolCompRotate that calls scale if SHIFT+CTRL + if (mask == (MASK_CONTROL | MASK_SHIFT)) + { + // If you have a scale tool, return that. Or else remove + // this if you don't want an override. + return LLToolCompScale::getInstance(); + } + // Otherwise fallback + return LLToolComposite::getOverrideTool(mask); +} + +//----------------------------------- +// Handle DoubleClick +bool FSToolCompPose::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!mManip->getSelection()->isEmpty() && + mManip->getHighlightedPart() == LLManip::LL_NO_PART) + { + // Possibly show some pose properties or open the pose floater + mPoser = dynamic_cast(LLFloaterReg::showInstance("fs_poser")); + return true; + } + else + { + // If nothing selected, try a mouse down again + return handleMouseDown(x, y, mask); + } +} + +//----------------------------------- +// render +void FSToolCompPose::render() +{ + // Render the current subtool + mCur->render(); + + // If the current subtool is not the manip, we can still + // optionally draw manip guidelines in the background + if (mCur != mManip) + { + mManip->renderGuidelines(); // or something similar if your manip has it + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + } +} diff --git a/indra/newview/lltoolcomp.h b/indra/newview/lltoolcomp.h index ca133a7b6a..fbce6f9079 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -252,5 +252,44 @@ protected: LLTool* mNull; }; +// Subclass of LLToolComposite +#include "fsmaniprotatejoint.h" // For FSManipRotateJoint +#include "fsfloaterposer.h" +class FSToolCompPose : public LLToolComposite +{ +public: + // Typical pattern: pass a name like "Pose" + FSToolCompPose(); + virtual ~FSToolCompPose(); + + // For some viewer patterns, we create a static singleton: + static FSToolCompPose* getInstance(); + + // Overriding base events: + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual void render() override; + void setAvatar(LLVOAvatar* avatar) { mManip->setAvatar(avatar); }; + void setJoint( LLJoint * joint ) { mManip->setJoint( joint ); }; + + // Optional override if you have SHIFT/CTRL combos + virtual LLTool* getOverrideTool(MASK mask) override; + + // The pick callback invoked on async pick + static void pickCallback(const LLPickInfo& pick_info); + void setPoserFloater(FSFloaterPoser* poser){ mPoser = poser; }; + FSFloaterPoser* getPoserFloater(){ return mPoser; }; +protected: + // Tools within this composite + FSManipRotateJoint* mManip = nullptr; + LLToolSelectRect* mSelectRect= nullptr; + FSFloaterPoser* mPoser = nullptr; + + // Track mouse state similarly to LLToolCompRotate + bool mMouseDown = false; +}; + #endif // LL_TOOLCOMP_H diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index 77f2d11abc..25f211c2ac 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -70,6 +70,7 @@ LLToolset* gCameraToolset = NULL; //LLToolset* gLandToolset = NULL; LLToolset* gMouselookToolset = NULL; LLToolset* gFaceEditToolset = NULL; +LLToolset* gPoserToolset = nullptr; ///////////////////////////////////////////////////// // LLToolMgr @@ -99,6 +100,8 @@ LLToolMgr::LLToolMgr() // gLandToolset = new LLToolset(); gMouselookToolset = new LLToolset(); gFaceEditToolset = new LLToolset(); + gPoserToolset = new LLToolset(); + gPoserToolset->setShowFloaterTools(false); gMouselookToolset->setShowFloaterTools(false); gFaceEditToolset->setShowFloaterTools(false); } @@ -121,6 +124,7 @@ void LLToolMgr::initTools() gMouselookToolset->addTool( LLToolCompGun::getInstance() ); gBasicToolset->addTool( LLToolCompInspect::getInstance() ); gFaceEditToolset->addTool( LLToolCamera::getInstance() ); + gPoserToolset->addTool( FSToolCompPose::getInstance() ); // On startup, use "select" tool setCurrentToolset(gBasicToolset); diff --git a/indra/newview/lltoolmgr.h b/indra/newview/lltoolmgr.h index d321bbb477..2ba4b8c809 100644 --- a/indra/newview/lltoolmgr.h +++ b/indra/newview/lltoolmgr.h @@ -129,6 +129,7 @@ extern LLToolset *gCameraToolset; //extern LLToolset *gLandToolset; extern LLToolset* gMouselookToolset; extern LLToolset* gFaceEditToolset; +extern LLToolset* gPoserToolset; extern LLTool* gToolNull; diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 3d762c5928..1cc89f21ca 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -475,7 +475,7 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (gResizeShadowTexture) { //skip render on frames where window has been resized gPipeline.resizeShadowTexture(); - gResizeShadowTexture = false; + // gResizeShadowTexture = false; // This prevents the deferred resize from working properly. } gSnapshot = for_snapshot; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 8c11b9ce2a..885d601bdd 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -5087,6 +5087,13 @@ void LLViewerWindow::renderSelections( bool for_gl_pick, bool pick_parcel_walls, // Call this once and only once LLSelectMgr::getInstance()->updateSilhouettes(); } + // render the poser manipulator guides + // if we have something selected those toosl should override + if ( (!for_hud) && (selection->isEmpty()) && (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPose::getInstance()) ) + { + FSToolCompPose::getInstance()->render(); + } + // // Draw fence around land selections if (for_gl_pick) diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index df72de54f0..2af6be9743 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1934,6 +1934,227 @@ void LLVOAvatar::renderBones(const std::string &selected_joint) } } +void LLVOAvatar::renderOnlySelectedBones(const std::vector &selected_joints) +{ + LLGLEnable blend(GL_BLEND); + + avatar_joint_list_t::iterator iter = mSkeleton.begin(); + avatar_joint_list_t::iterator end = mSkeleton.end(); + + // For selected joints + static LLVector3 SELECTED_COLOR_OCCLUDED(1.0f, 1.0f, 0.0f); + static LLVector3 SELECTED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + // For bones with position overrides defined + static LLVector3 OVERRIDE_COLOR_OCCLUDED(1.0f, 0.0f, 0.0f); + static LLVector3 OVERRIDE_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + // For bones which are rigged to by at least one attachment + static LLVector3 RIGGED_COLOR_OCCLUDED(0.0f, 1.0f, 1.0f); + static LLVector3 RIGGED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + // For bones not otherwise colored + static LLVector3 OTHER_COLOR_OCCLUDED(0.0f, 1.0f, 0.0f); + static LLVector3 OTHER_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + + static F32 SPHERE_SCALEF = 0.001f; + + for (; iter != end; ++iter) + { + LLJoint* jointp = *iter; + if (!jointp) + { + continue; + } + if (std::find(selected_joints.begin(), selected_joints.end(), jointp->getName()) == selected_joints.end()) + { + continue; + } + jointp->updateWorldMatrix(); + + LLVector3 occ_color, visible_color; + + occ_color = SELECTED_COLOR_OCCLUDED; + visible_color = SELECTED_COLOR_VISIBLE; + + gGL.pushMatrix(); + gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] ); + + gGL.diffuseColor3f( 1.f, 0.f, 1.f ); + + gGL.begin(LLRender::LINES); + + LLVector3 v[] = + { + LLVector3(1,0,0), + LLVector3(-1,0,0), + LLVector3(0,1,0), + LLVector3(0,-1,0), + + LLVector3(0,0,-1), + LLVector3(0,0,1), + }; + + //sides + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[2].mV); + + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[3].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[2].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[3].mV); + + + //top + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[2].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[3].mV); + gGL.vertex3fv(v[4].mV); + + + //bottom + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[2].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[3].mV); + gGL.vertex3fv(v[5].mV); + + gGL.end(); + + gGL.popMatrix(); + + + // renderBoxAroundJointAttachments( jointp ); + + } + + + // // draw joint space bounding boxes of rigged attachments in yellow + // gGL.color3f(1.f, 1.f, 0.f); + // for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) + // { + // LLJoint* joint = getJoint(joint_num); + // LLJointRiggingInfo* rig_info = NULL; + // if (joint_num < mJointRiggingInfoTab.size()) + // { + // rig_info = &mJointRiggingInfoTab[joint_num]; + // } + + // if (joint && rig_info && rig_info->isRiggedTo()) + // { + // LLViewerJointAttachment* as_joint_attach = dynamic_cast(joint); + // if (as_joint_attach && as_joint_attach->getIsHUDAttachment()) + // { + // // Ignore bounding box of HUD joints + // continue; + // } + // gGL.pushMatrix(); + // gGL.multMatrix(&joint->getXform()->getWorldMatrix().mMatrix[0][0]); + + // LLVector4a pos; + // LLVector4a size; + + // const LLVector4a* extents = rig_info->getRiggedExtents(); + + // pos.setAdd(extents[0], extents[1]); + // pos.mul(0.5f); + // size.setSub(extents[1], extents[0]); + // size.mul(0.5f); + + // drawBoxOutline(pos, size); + + // gGL.popMatrix(); + // } + // } + + // draw world space attachment rigged bounding boxes in cyan + // gGL.color3f(0.f, 1.f, 1.f); + // for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + // iter != mAttachmentPoints.end(); + // ++iter) + // { + // LLViewerJointAttachment* attachment = iter->second; + + // if (attachment->getValid()) + // { + // for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + // attachment_iter != attachment->mAttachedObjects.end(); + // ++attachment_iter) + // { + // LLViewerObject* attached_object = attachment_iter->get(); + // if (attached_object && !attached_object->isHUDAttachment()) + // { + // LLDrawable* drawable = attached_object->mDrawable; + // if (drawable && drawable->isState(LLDrawable::RIGGED | LLDrawable::RIGGED_CHILD)) + // { + // // get face rigged extents + // for (S32 i = 0; i < drawable->getNumFaces(); ++i) + // { + // LLFace* facep = drawable->getFace(i); + // if (facep && facep->isState(LLFace::RIGGED)) + // { + // LLVector4a center, size; + + // LLVector4a* extents = facep->mRiggedExtents; + + // center.setAdd(extents[0], extents[1]); + // center.mul(0.5f); + // size.setSub(extents[1], extents[0]); + // size.mul(0.5f); + // drawBoxOutline(center, size); + // } + // } + // } + // } + // } + // } + // } +} +void LLVOAvatar::renderBoxAroundJointAttachments(LLJoint * joint) +{ + LLJointRiggingInfo* rig_info = NULL; + + if (joint->getJointNum() < mJointRiggingInfoTab.size()) + { + rig_info = &mJointRiggingInfoTab[joint->getJointNum()]; + } + + if (joint && rig_info && rig_info->isRiggedTo()) + { + LLViewerJointAttachment* as_joint_attach = dynamic_cast(joint); + gGL.pushMatrix(); + gGL.multMatrix(&joint->getXform()->getWorldMatrix().mMatrix[0][0]); + + LLVector4a pos; + LLVector4a size; + + const LLVector4a* extents = rig_info->getRiggedExtents(); + + pos.setAdd(extents[0], extents[1]); + pos.mul(0.5f); + size.setSub(extents[1], extents[0]); + size.mul(0.5f); + + drawBoxOutline(pos, size); + + gGL.popMatrix(); + } +} + void LLVOAvatar::renderJoints() { diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 5a7a6541e3..5051698e22 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -569,6 +569,9 @@ public: U32 renderTransparent(bool first_pass); void renderCollisionVolumes(); void renderBones(const std::string &selected_joint = std::string()); + void renderOnlySelectedBones(const std::vector &selected_joints); + void renderBoxAroundJointAttachments(LLJoint * joint); + void renderJoints(); static void deleteCachedImages(bool clearAll=true); static void destroyGL(); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index a6316f0ca3..4dcd19bf32 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -789,6 +789,12 @@ void LLPipeline::requestResizeShadowTexture() void LLPipeline::resizeShadowTexture() { + // [FIRE-33200] changing shadowres requires reload - original fix by William Weaver (paperwork) + if(mRT->width == 0 || mRT->height == 0) + { + return; + } + // releaseSunShadowTargets(); releaseSpotShadowTargets(); allocateShadowBuffer(mRT->width, mRT->height); @@ -8093,7 +8099,7 @@ bool LLPipeline::renderSnapshotFrame(LLRenderTarget* src, LLRenderTarget* dst) { float frame_width = w; float frame_height = frame_width / snapshot_aspect; - // Centre this box in [0..1][0..1] + // Centre this box in [0..1]x[0..1] float y_offset = 0.5f * (h - frame_height); left = 0.f; top = y_offset / h; @@ -8104,7 +8110,7 @@ bool LLPipeline::renderSnapshotFrame(LLRenderTarget* src, LLRenderTarget* dst) { float frame_height = h; float frame_width = h * snapshot_aspect; - // Centre this box in [0..1][0..1] + // Centre this box in [0..1]x[0..1] float x_offset = 0.5f * (w - frame_width); left = x_offset / w; top = 0.f; @@ -8807,14 +8813,20 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_ shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_NOISE, RenderShadowNoise); shader.uniform1f(LLShaderMgr::DEFERRED_BLUR_SIZE, RenderShadowBlurSize); - shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_RADIUS, RenderSSAOScale); - shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_MAX_RADIUS, (GLfloat)RenderSSAOMaxScale); +// Compute scale factor to match AO appearance between view and snapshot. + F32 screen_to_target_scale_factor = (F32)gViewerWindow->getWindowHeightRaw() / deferred_target->getHeight(); + //shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_RADIUS, RenderSSAOScale); + shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_RADIUS, RenderSSAOScale / screen_to_target_scale_factor); + //shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_MAX_RADIUS, (GLfloat)RenderSSAOMaxScale); + shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_MAX_RADIUS, RenderSSAOMaxScale / screen_to_target_scale_factor); + // F32 ssao_factor = RenderSSAOFactor; shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_FACTOR, ssao_factor); shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_FACTOR_INV, 1.0f/ssao_factor); LLVector3 ssao_effect = RenderSSAOEffect; + F32 matrix_diag = (ssao_effect[0] + 2.0f*ssao_effect[1])/3.0f; F32 matrix_nondiag = (ssao_effect[0] - ssao_effect[1])/3.0f; // This matrix scales (proj of color onto <1/rt(3),1/rt(3),1/rt(3)>) by diff --git a/indra/newview/skins/default/textures/icons/visual_pose_enabled.png b/indra/newview/skins/default/textures/icons/visual_pose_enabled.png new file mode 100644 index 0000000000..eee9a94477 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/visual_pose_enabled.png differ diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 0230309383..da82bd327a 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -593,6 +593,11 @@ with the same filename but different name + + + + + diff --git a/indra/newview/skins/default/xui/az/panel_region_environment.xml b/indra/newview/skins/default/xui/az/panel_region_environment.xml index 77ee5b5bcc..66689e7a1b 100644 --- a/indra/newview/skins/default/xui/az/panel_region_environment.xml +++ b/indra/newview/skins/default/xui/az/panel_region_environment.xml @@ -72,8 +72,7 @@ - Səma [INDEX] - [ALTITUDE]m + Səma [INDEX] [ALTITUDE]m Naməlum @@ -82,8 +81,7 @@ - Səma [INDEX] - [ALTITUDE]m + Səma [INDEX] [ALTITUDE]m Naməlum @@ -92,8 +90,7 @@ - Səma [INDEX] - [ALTITUDE]m + Səma [INDEX] [ALTITUDE]m Naməlum diff --git a/indra/newview/skins/default/xui/de/floater_fs_poser.xml b/indra/newview/skins/default/xui/de/floater_fs_poser.xml index 4d2b073034..d2dd90388f 100644 --- a/indra/newview/skins/default/xui/de/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/de/floater_fs_poser.xml @@ -1,153 +1,151 @@ - Körper - Stirn/Augenbrauen - Augen/Augenlider - Wangen/Lippen - Linke Hand - Linker Arm - Rechte Hand - Rechter Arm - Beine - Schwanz - Hintere Glieder - Flügel - Ohren/Nase + Körper + Stirn/Augenbrauen + Augen/Augenlider + Wangen/Lippen + Linke Hand + Linker Arm + Rechte Hand + Rechter Arm + Beine + Schwanz + Hintere Glieder + Flügel + Ohren/Nase - Ganzer Avatar - Oberkörper - Brust - Hals - Kopf - Rechtes Auge - Linkes Auge - Linker Stirnwinkel - Rechter Stirnwinkel - Äuß. linke Augenbraue - Mit. linke Augenbraue - In. linke Augenbraue - Äuß. rechte Augenbraue - Mit. rechte Augenbraue - In. rechte Augenbraue - Linkes oberes Augenlid - Linkes unteres Augenlid - Rechtes oberes Augenlid - Rechtes unteres Augenlid - Linkes oberes Ohr - Linkes unteres Ohr - Rechtes oberes Ohr - Rechtes unteres Ohr - Nase links - Nase Mitte - Nase rechts - Linke obere Wange - Linke untere Wange - Rechte oberes Wange - Rechte untere Wange - Kiefer - Zähne unten - Linke untere Lippe - Rechte untere Lippe - Mittlere untere Lippe - Zunge Basis - Zungenspite - Kieferform - Stirn Mitte - Nase Basis - Obere Zähne - Linke obere Lippe - Rechte obere Lippe - Linker Mundwinkel - Rechter Mundwinkel - Mittlere obere Lippe - In. linker Augenwinkel - In. rechter Augenwinkel - Nasenbrücke - Kragen - Ganzer Arm - Unterarm - Handgelenk - Basis Mittelfinger - Mitte Mittelfinger - Spitze Mittelfinger - Basis Zeigefinger - Mitte Zeigefinger - Spitze Zeigefinger - Basis Ringfinger - Mitte Ringfinger - Spitze Ringfinger - Basis kleiner Finger - Mitte kleiner Finger - Spitze kleiner Finger - Basis Daumen - Mitte Daumen - Spitze Daumen - Kragen - Ganzer Arm - Unterarm - Handgelenk - Basis Mittelfinger - Mitte Mittelfinger - Spitze Mittelfinger - Basis Zeigefinger - Mitte Zeigefinger - Spitze Zeigefinger - Basis Ringfinger - Mitte Ringfinger - Spitze Ringfinger - Basis kleiner Finger - Mitte kleiner Finger - Spitze kleiner Finger - Basis Daumen - Mitte Daumen - Spitze Daumen - Wurzel - Links 1 - Links 2 - Links 3 - Links 4 - Linker Fächer - Rechts 1 - Rechts 2 - Rechts 3 - Rechts 4 - Rechter Fächer - Rechtes Bein - Rechtes Knie - Rechter Knöchel - Rechter Fuß - Rechter Zeh - Linkes Bein - Linkes Knie - Linker Knöchel - Linker Fuß - Linker Zeh - Basis Schwanz - Schwanz 2 - Schwanz 3 - Schwanz 4 - Schwanz 5 - Schwanz-Spitze - Leiste - Wurzel - Linke Basis - Links 2 - Links 3 - Links 4 - Rechte Basis - Rechts 2 - Rechts 3 - Rechts 4 - Hintern - Bauch - Linke Brustmuskeln - Rechte Brustmuskeln - Linke Basis - Rechte Basis - Linke Spitze - Rechte Spitze + Ganzer Avatar + Oberkörper + Brust + Hals + Kopf + Rechtes Auge + Linkes Auge + Linker Stirnwinkel + Rechter Stirnwinkel + Äuß. linke Augenbraue + Mit. linke Augenbraue + In. linke Augenbraue + Äuß. rechte Augenbraue + Mit. rechte Augenbraue + In. rechte Augenbraue + Linkes oberes Augenlid + Linkes unteres Augenlid + Rechtes oberes Augenlid + Rechtes unteres Augenlid + Linkes oberes Ohr + Linkes unteres Ohr + Rechtes oberes Ohr + Rechtes unteres Ohr + Nase links + Nase Mitte + Nase rechts + Linke obere Wange + Linke untere Wange + Rechte oberes Wange + Rechte untere Wange + Kiefer + Zähne unten + Linke untere Lippe + Rechte untere Lippe + Mittlere untere Lippe + Zunge Basis + Zungenspite + Kieferform + Stirn Mitte + Nase Basis + Obere Zähne + Linke obere Lippe + Rechte obere Lippe + Linker Mundwinkel + Rechter Mundwinkel + Mittlere obere Lippe + In. linker Augenwinkel + In. rechter Augenwinkel + Nasenbrücke + Kragen + Ganzer Arm + Unterarm + Handgelenk + Basis Mittelfinger + Mitte Mittelfinger + Spitze Mittelfinger + Basis Zeigefinger + Mitte Zeigefinger + Spitze Zeigefinger + Basis Ringfinger + Mitte Ringfinger + Spitze Ringfinger + Basis kleiner Finger + Mitte kleiner Finger + Spitze kleiner Finger + Basis Daumen + Mitte Daumen + Spitze Daumen + Kragen + Ganzer Arm + Unterarm + Handgelenk + Basis Mittelfinger + Mitte Mittelfinger + Spitze Mittelfinger + Basis Zeigefinger + Mitte Zeigefinger + Spitze Zeigefinger + Basis Ringfinger + Mitte Ringfinger + Spitze Ringfinger + Basis kleiner Finger + Mitte kleiner Finger + Spitze kleiner Finger + Basis Daumen + Mitte Daumen + Spitze Daumen + Wurzel + Links 1 + Links 2 + Links 3 + Links 4 + Linker Fächer + Rechts 1 + Rechts 2 + Rechts 3 + Rechts 4 + Rechter Fächer + Rechtes Bein + Rechtes Knie + Rechter Knöchel + Rechter Fuß + Rechter Zeh + Linkes Bein + Linkes Knie + Linker Knöchel + Linker Fuß + Linker Zeh + Basis Schwanz + Schwanz 2 + Schwanz 3 + Schwanz 4 + Schwanz 5 + Schwanz-Spitze + Leiste + Wurzel + Linke Basis + Links 2 + Links 3 + Links 4 + Rechte Basis + Rechts 2 + Rechts 3 + Rechts 4 + Hintern + Bauch + Linke Brustmuskeln + Rechte Brustmuskeln + Pose laden + Diff. laden @@ -221,34 +219,59 @@ Steuerkugel-Sensitivität: + + - + - Rotation: - - + name="modifier_tabs" + tab_height="20" + tab_width="90" + tab_group="1" + tab_position="top" + top_pad="0" + width="215"> + + + + Up/Down: + + + Left/Right: + + + Roll: + + + + + + + + + + + + + + Position X: + + + + + + + + + Position Y: + + + + + + + + + Position Z: + + + + + + + + + Scale X: + + + + + + + + + Scale Y: + + + + + + + + + Scale Z: + + + + + + + + + + follows="left|top|bottom" + height="44" + background_visible="false" + layout="topleft" + mouse_opaque="false" + left="0" + top_pad="2" + name="trackball_button_panel" + width="232"> + + - - Up/Down: - - - - - - - Left/Right: - - - - - - - Roll: - - - - - background_visible="false" layout="topleft" mouse_opaque="false" - left_pad="2" + left_pad="-26" visible="false" name="poses_loadSave" top="0" width="225"> left="0" name="button_controls_panel" width="800"> - left_pad="0" top_delta="0" name="advbutton_spacer_panel" - width="36"/> + width="56" + top_pad="-1" + left="2"/> @@ -1390,7 +1669,7 @@ width="403"> name="load_poses_button" left_pad="1" top_delta="0" - width="97"/> + width="95"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/indra/newview/skins/default/xui/es/panel_region_environment.xml b/indra/newview/skins/default/xui/es/panel_region_environment.xml index 2649187bab..6894be6bb8 100644 --- a/indra/newview/skins/default/xui/es/panel_region_environment.xml +++ b/indra/newview/skins/default/xui/es/panel_region_environment.xml @@ -32,20 +32,17 @@ - Cielo [INDEX] - [ALTITUDE]m + Cielo [INDEX] [ALTITUDE]m Desconocido - Cielo [INDEX] - [ALTITUDE]m + Cielo [INDEX] [ALTITUDE]m Desconocido - Cielo [INDEX] - [ALTITUDE]m + Cielo [INDEX] [ALTITUDE]m Desconocido diff --git a/indra/newview/skins/default/xui/fr/floater_fs_poser.xml b/indra/newview/skins/default/xui/fr/floater_fs_poser.xml index 138f6683c1..f6dcc938d6 100644 --- a/indra/newview/skins/default/xui/fr/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/fr/floater_fs_poser.xml @@ -141,10 +141,6 @@ Ventre Sein gauche Sein droit - Base gauche - Base droite - Pointe gauche - Pointe droite @@ -212,34 +208,59 @@ Sensibilité du trackpad : + - + +