diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index a4ab4a66a7..9aae2a38c6 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 118f209adb..45d2941acf 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -26198,5 +26198,16 @@ Change of this parameter will affect the layout of buttons in notification toast
Value
1.0
+ FSManipRotateJointUseNaturalDirection
+
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 d27c8432f6..6d03278494 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -771,7 +771,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 5d7f76efcd..386788bd99 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -5096,6 +5096,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 a5ccda085c..d8875ce413 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 ee79a86187..70b8a5fba7 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -568,6 +568,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 f27fe3dee1..cfbbfa42b8 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);
@@ -2302,9 +2308,9 @@ F32 LLPipeline::calcPixelArea(const LLVector4a& center, const LLVector4a& size,
//shrink dist by dist/16.
if (dist < 16.f)
{
- dist /= 16.f;
- dist *= dist;
- dist *= 16.f;
+ dist /= 16.f;
+ dist *= dist;
+ dist *= 16.f;
}
//get area of circle around node
@@ -8082,7 +8088,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;
@@ -8093,7 +8099,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;
@@ -8796,14 +8802,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:
-
-
+
+
+
+
+
+ Auf/Ab:
+
+
+ Links/Re.:
+
+
+ Rollen:
+
+
+
+
+ Position X:
+
+
+ Position Y:
+
+
+ Position Z:
+
+
+ Skala X:
+
+
+ Skala Y:
+
+
+ Skala Z:
+
+
+
-
+
+
+
-
- Hoch/Runter:
-
-
- Links/Rechts:
-
-
- Rollen:
-
@@ -259,7 +282,6 @@
-
@@ -270,27 +292,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/indra/newview/skins/default/xui/de/panel_region_environment.xml b/indra/newview/skins/default/xui/de/panel_region_environment.xml
index 9a8fefd24e..fbc88f4d85 100644
--- a/indra/newview/skins/default/xui/de/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/de/panel_region_environment.xml
@@ -80,8 +80,7 @@
- Himmel [INDEX]
- [ALTITUDE]m
+ Himmel [INDEX]
[ALTITUDE]m
Unbekannt
@@ -90,8 +89,7 @@
- Himmel [INDEX]
- [ALTITUDE]m
+ Himmel [INDEX]
[ALTITUDE]m
Unbekannt
@@ -100,8 +98,7 @@
- Himmel [INDEX]
- [ALTITUDE]m
+ Himmel [INDEX]
[ALTITUDE]m
Unbekannt
diff --git a/indra/newview/skins/default/xui/en/floater_fs_poser.xml b/indra/newview/skins/default/xui/en/floater_fs_poser.xml
index 3ec5684a08..2a12eb19a9 100644
--- a/indra/newview/skins/default/xui/en/floater_fs_poser.xml
+++ b/indra/newview/skins/default/xui/en/floater_fs_poser.xml
@@ -1,289 +1,289 @@
- Inv_BodyShape
-
- Inv_Object
- Check_Mark
+width="430">
+ Inv_BodyShape
+
+ Inv_Object
+ Check_Mark
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
- SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
- SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
- SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
- NEGATE_PITCH
- NEGATE_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_ROLL NEGATE_PITCH
- SWAP_ROLL_AND_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_PITCH
- SWAP_ROLL_AND_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_ROLL
- NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- NEGATE_PITCH
- NEGATE_PITCH NEGATE_ROLL
- NEGATE_PITCH
- NEGATE_PITCH
- NEGATE_PITCH NEGATE_ROLL
- NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- NEGATE_PITCH
- NEGATE_PITCH
- NEGATE_PITCH
- NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_ROLL_AND_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_PITCH
- SWAP_ROLL_AND_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_PITCH
- SWAP_ROLL_AND_PITCH
- SWAP_ROLL_AND_PITCH NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_X2Z_Y2X_Z2Y
- SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
- SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
+ SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
+ SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
+ SWAP_YAW_AND_ROLL NEGATE_PITCH NEGATE_ROLL
+ NEGATE_PITCH
+ NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_ROLL NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_ROLL
+ NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ NEGATE_PITCH
+ NEGATE_PITCH NEGATE_ROLL
+ NEGATE_PITCH
+ NEGATE_PITCH
+ NEGATE_PITCH NEGATE_ROLL
+ NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ NEGATE_PITCH
+ NEGATE_PITCH
+ NEGATE_PITCH
+ NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_PITCH
+ SWAP_ROLL_AND_PITCH
+ SWAP_ROLL_AND_PITCH NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_X2Z_Y2X_Z2Y
+ SWAP_X2Z_Y2X_Z2Y NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
+ SWAP_YAW_AND_ROLL NEGATE_PITCH
- true
- true
- true
- true
- true
- true
- true
- true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
- Body
- Forehead/Brows
- Eyes/Eye Lids
- Cheeks/Lips
- Left Hand
- Left Arm
- Right Hand
- Right Arm
- Legs
- Tail
- Hind Limbs
- Wings
- Ears/Nose
+ Body
+ Forehead/Brows
+ Eyes/Eye Lids
+ Cheeks/Lips
+ Left Hand
+ Left Arm
+ Right Hand
+ Right Arm
+ Legs
+ Tail
+ Hind Limbs
+ Wings
+ Ears/Nose
- Whole Avatar
- Torso
- Chest
- Neck
- Head
- Right Eye
- Left Eye
- Forehead Left Side
- Forehead Right Side
- Eyebrow Outer Left
- Eyebrow Middle Left
- Eyebrow Inner Left
- Eyebrow Outer Right
- Eyebrow Middle Right
- Eyebrow Inner Right
- EyeLid Upper Left
- EyeLid Lower Left
- EyeLid Upper Right
- EyeLid Lower Right
- Ear Upper Left
- Ear Lower Left
- Ear Upper Right
- Ear Lower Right
- Nose Left
- Nose Middle
- Nose Right
- Cheek Lower Left
- Cheek Upper Left
- Cheek Lower Right
- Cheek Upper Right
- Jaw
- Teeth Lower
- Lip Lower Left
- Lip Lower Right
- Lip Lower Middle
- Tongue Base
- Tongue Tip
- Jaw Shaper
- Forehead Middle
- Nose Base
- Teeth Upper
- Lip Upper Left
- Lip Upper Right
- Lip Corner Left
- Lip Corner Right
- Lip Upper Middle
- Eye corner Inner Left
- Eye corner Inner Right
- Nose Bridge
- Collar
- Whole Arm
- Forearm
- Wrist
- Middle Finger Base
- Middle Finger Mid
- Middle Finger Tip
- Index Finger Base
- Index Finger Mid
- Index Finger Tip
- Ring Finger Base
- Ring Finger Mid
- Ring Finger Tip
- Pinky Finger Base
- Pinky Finger Mid
- Pinky Finger Tip
- Thumb Base
- Thumb Mid
- Thumb Tip
- Collar
- Whole Arm
- Forearm
- Wrist
- Middle Finger Base
- Middle Finger Mid
- Middle Finger Tip
- Index Finger Base
- Index Finger Mid
- Index Finger Tip
- Ring Finger Base
- Ring Finger Mid
- Ring Finger Tip
- Pinky Finger Base
- Pinky Finger Mid
- Pinky Finger Tip
- Thumb Base
- Thumb Mid
- Thumb Tip
- Root
- Left 1
- Left 2
- Left 3
- Left 4
- Left Fan
- Right 1
- Right 2
- Right 3
- Right 4
- Right Fan
- Right Whole Leg
- Right Knee
- Right Ankle
- Right Foot
- Right Toe
- Left Whole Leg
- Left Knee
- Left Ankle
- Left Foot
- Left Toe
- Tail Base
- Tail 2
- Tail 3
- Tail 4
- Tail 5
- Tail Tip
- Groin
- Root
- Left Base
- Left 2
- Left 3
- Left 4
- Right Base
- Right 2
- Right 3
- Right 4
- Bottom
- Belly
- Left Pec
- Right Pec
- Left Base
- Right Base
- Left Tip
- Right Tip
+ Whole Avatar
+ Torso
+ Chest
+ Neck
+ Head
+ Right Eye
+ Left Eye
+ Forehead Left Side
+ Forehead Right Side
+ Eyebrow Outer Left
+ Eyebrow Middle Left
+ Eyebrow Inner Left
+ Eyebrow Outer Right
+ Eyebrow Middle Right
+ Eyebrow Inner Right
+ EyeLid Upper Left
+ EyeLid Lower Left
+ EyeLid Upper Right
+ EyeLid Lower Right
+ Ear Upper Left
+ Ear Lower Left
+ Ear Upper Right
+ Ear Lower Right
+ Nose Left
+ Nose Middle
+ Nose Right
+ Cheek Lower Left
+ Cheek Upper Left
+ Cheek Lower Right
+ Cheek Upper Right
+ Jaw
+ Teeth Lower
+ Lip Lower Left
+ Lip Lower Right
+ Lip Lower Middle
+ Tongue Base
+ Tongue Tip
+ Jaw Shaper
+ Forehead Middle
+ Nose Base
+ Teeth Upper
+ Lip Upper Left
+ Lip Upper Right
+ Lip Corner Left
+ Lip Corner Right
+ Lip Upper Middle
+ Eye corner Inner Left
+ Eye corner Inner Right
+ Nose Bridge
+ Collar
+ Whole Arm
+ Forearm
+ Wrist
+ Middle Finger Base
+ Middle Finger Mid
+ Middle Finger Tip
+ Index Finger Base
+ Index Finger Mid
+ Index Finger Tip
+ Ring Finger Base
+ Ring Finger Mid
+ Ring Finger Tip
+ Pinky Finger Base
+ Pinky Finger Mid
+ Pinky Finger Tip
+ Thumb Base
+ Thumb Mid
+ Thumb Tip
+ Collar
+ Whole Arm
+ Forearm
+ Wrist
+ Middle Finger Base
+ Middle Finger Mid
+ Middle Finger Tip
+ Index Finger Base
+ Index Finger Mid
+ Index Finger Tip
+ Ring Finger Base
+ Ring Finger Mid
+ Ring Finger Tip
+ Pinky Finger Base
+ Pinky Finger Mid
+ Pinky Finger Tip
+ Thumb Base
+ Thumb Mid
+ Thumb Tip
+ Root
+ Left 1
+ Left 2
+ Left 3
+ Left 4
+ Left Fan
+ Right 1
+ Right 2
+ Right 3
+ Right 4
+ Right Fan
+ Right Whole Leg
+ Right Knee
+ Right Ankle
+ Right Foot
+ Right Toe
+ Left Whole Leg
+ Left Knee
+ Left Ankle
+ Left Foot
+ Left Toe
+ Tail Base
+ Tail 2
+ Tail 3
+ Tail 4
+ Tail 5
+ Tail Tip
+ Groin
+ Root
+ Left Base
+ Left 2
+ Left 3
+ Left 4
+ Right Base
+ Right 2
+ Right 3
+ Right 4
+ Bottom
+ Belly
+ Left Pec
+ Right Pec
- SWAP_X2Y_Y2Z_Z2X
+ SWAP_X2Y_Y2Z_Z2X
+
+ Load Pose
+ Load Diff
name="poser_stack"
orientation="vertical"
top="0"
- width="607">
+ width="700">
+ height="310"
+ width="700">
function="Poser.CommitSpinner"
parameter="3"/>
+
width="134" />
+
-
+
- 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"/>
-
+
\ No newline at end of file
diff --git a/indra/newview/skins/default/xui/en/panel_region_environment.xml b/indra/newview/skins/default/xui/en/panel_region_environment.xml
index 0a7a597ce5..e13c6a03f1 100644
--- a/indra/newview/skins/default/xui/en/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/en/panel_region_environment.xml
@@ -272,8 +272,7 @@
top_pad="1"
halign="right"
name="txt_alt1">
- Sky [INDEX]
- [ALTITUDE]m
+ Sky [INDEX]
[ALTITUDE]m
- Sky [INDEX]
- [ALTITUDE]m
+ Sky [INDEX]
[ALTITUDE]m
- Sky [INDEX]
- [ALTITUDE]m
+ Sky [INDEX]
[ALTITUDE]m
- 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 :
+
-
+
+
-
-
- Rotation :
-
-
+
+
+
+
+
+ Haut/Bas :
+
+
+ Gche/drte :
+
+
+ Roulement :
+
+
+
+
+ Position X :
+
+
+ Position Y :
+
+
+ Position Z :
+
+
+ Échelle X :
+
+
+ Échelle Y :
+
+
+ Échelle Z :
+
+
+
-
+
-
+
+
+
-
- Haut/Bas :
-
-
- Gauche/droite :
-
-
- Roulement :
-
@@ -250,7 +271,6 @@
-
@@ -261,27 +281,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/indra/newview/skins/default/xui/fr/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/fr/floater_preferences_graphics_advanced.xml
index fd15c80e24..44ca063da9 100644
--- a/indra/newview/skins/default/xui/fr/floater_preferences_graphics_advanced.xml
+++ b/indra/newview/skins/default/xui/fr/floater_preferences_graphics_advanced.xml
@@ -1,5 +1,5 @@
-
+
Général
@@ -13,6 +13,10 @@
Faible
+
+ Résolution maximale du LOD :
+
+
Avatar
@@ -62,7 +66,7 @@
- Qualité de l'anti-crénelage :
+ Qualité anti-crénelage :
@@ -168,7 +172,7 @@
-
+
Mappage de tons :
diff --git a/indra/newview/skins/default/xui/fr/floater_snapshot.xml b/indra/newview/skins/default/xui/fr/floater_snapshot.xml
index 161ba064ac..f282636dfd 100644
--- a/indra/newview/skins/default/xui/fr/floater_snapshot.xml
+++ b/indra/newview/skins/default/xui/fr/floater_snapshot.xml
@@ -29,6 +29,7 @@
+
Filtre :
diff --git a/indra/newview/skins/default/xui/fr/menu_login.xml b/indra/newview/skins/default/xui/fr/menu_login.xml
index ee3765a3a1..2cb180dcc8 100644
--- a/indra/newview/skins/default/xui/fr/menu_login.xml
+++ b/indra/newview/skins/default/xui/fr/menu_login.xml
@@ -1,12 +1,13 @@
-
- Rendu des masques alphas :
-
Divers :
+
+
diff --git a/indra/newview/skins/default/xui/fr/panel_region_environment.xml b/indra/newview/skins/default/xui/fr/panel_region_environment.xml
index 21949f9733..d8035e2e67 100644
--- a/indra/newview/skins/default/xui/fr/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/fr/panel_region_environment.xml
@@ -73,8 +73,7 @@
- Ciel [INDEX]
- [ALTITUDE]m
+ Ciel [INDEX]
[ALTITUDE] m
Inconnu
@@ -83,8 +82,7 @@
- Ciel [INDEX]
- [ALTITUDE]m
+ Ciel [INDEX]
[ALTITUDE] m
Inconnu
@@ -93,8 +91,7 @@
- Ciel [INDEX]
- [ALTITUDE]m
+ Ciel [INDEX]
[ALTITUDE] m
Inconnu
diff --git a/indra/newview/skins/default/xui/it/floater_fs_poser.xml b/indra/newview/skins/default/xui/it/floater_fs_poser.xml
index 6eeeb7e882..adaa900d87 100644
--- a/indra/newview/skins/default/xui/it/floater_fs_poser.xml
+++ b/indra/newview/skins/default/xui/it/floater_fs_poser.xml
@@ -141,10 +141,6 @@
Pancia
Pettorale sinistro
Pettorale destro
- Orecchio sx: superiore
- Orecchio dx: superiore
- Orecchio sx: inferiore
- Orecchio dx: inferiore
diff --git a/indra/newview/skins/default/xui/it/panel_region_environment.xml b/indra/newview/skins/default/xui/it/panel_region_environment.xml
index 6beee6427d..5756ad7926 100644
--- a/indra/newview/skins/default/xui/it/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/it/panel_region_environment.xml
@@ -70,8 +70,7 @@
- Cielo [INDEX]
- [ALTITUDE]m
+ Cielo [INDEX]
[ALTITUDE]m
Sconosciuto
@@ -80,8 +79,7 @@
- Cielo [INDEX]
- [ALTITUDE]m
+ Cielo [INDEX]
[ALTITUDE]m
Sconosciuto
@@ -90,8 +88,7 @@
- Cielo [INDEX]
- [ALTITUDE]m
+ Cielo [INDEX]
[ALTITUDE]m
Sconosciuto
diff --git a/indra/newview/skins/default/xui/ja/panel_region_environment.xml b/indra/newview/skins/default/xui/ja/panel_region_environment.xml
index d014e53126..ba1be072bc 100644
--- a/indra/newview/skins/default/xui/ja/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/ja/panel_region_environment.xml
@@ -71,8 +71,7 @@
- 空 [INDEX]
- [ALTITUDE]m
+ 空 [INDEX]
[ALTITUDE]m
不明
@@ -81,8 +80,7 @@
- 空 [INDEX]
- [ALTITUDE]m
+ 空 [INDEX]
[ALTITUDE]m
不明
@@ -91,8 +89,7 @@
- 空 [INDEX]
- [ALTITUDE]m
+ 空 [INDEX]
[ALTITUDE]m
不明
diff --git a/indra/newview/skins/default/xui/pl/floater_fs_poser.xml b/indra/newview/skins/default/xui/pl/floater_fs_poser.xml
index a58b91988f..21eeade61e 100644
--- a/indra/newview/skins/default/xui/pl/floater_fs_poser.xml
+++ b/indra/newview/skins/default/xui/pl/floater_fs_poser.xml
@@ -141,10 +141,8 @@
Brzuch
Lewa pierś
Prawa pierś
- Lewa podstawa
- Prawa podstawa
- Lewy czubek
- Prawy czubek
+ Ładuj pozę
+ Ładuj różn.
@@ -213,34 +211,59 @@
Czułość trackpada:
+
+
-
-
- Obrót:
-
-
+
+
+
+
+
+ Góra/Dół:
+
+
+ Lewo/Prawo:
+
+
+ Zawijanie:
+
+
+
+
+ Pozycja X:
+
+
+ Pozycja Y:
+
+
+ Pozycja Z:
+
+
+ Skala X:
+
+
+ Skala Y:
+
+
+ Skala Z:
+
+
+
-
+
+
+
-
- Góra/Dół:
-
-
- Lewo/Prawo:
-
-
- Rolowanie:
-
@@ -251,38 +274,15 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/indra/newview/skins/default/xui/pl/floater_phototools.xml b/indra/newview/skins/default/xui/pl/floater_phototools.xml
index 76e2f74d7b..e0afbb5689 100644
--- a/indra/newview/skins/default/xui/pl/floater_phototools.xml
+++ b/indra/newview/skins/default/xui/pl/floater_phototools.xml
@@ -30,7 +30,7 @@
-
+
@@ -107,7 +107,7 @@
- Oświetl. punktowe (restart)
+ Światła (restart)
@@ -146,7 +146,7 @@
- Przesun. pnkt.
+ Przesn. pnkt.
diff --git a/indra/newview/skins/default/xui/pl/panel_region_environment.xml b/indra/newview/skins/default/xui/pl/panel_region_environment.xml
index d45d0eeb2a..f7fec6a304 100644
--- a/indra/newview/skins/default/xui/pl/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/pl/panel_region_environment.xml
@@ -67,8 +67,7 @@
- Niebo [INDEX]
- [ALTITUDE]m
+ Niebo [INDEX]
[ALTITUDE]m
Nieznane
@@ -77,8 +76,7 @@
- Niebo [INDEX]
- [ALTITUDE]m
+ Niebo [INDEX]
[ALTITUDE]m
Nieznane
@@ -87,8 +85,7 @@
- Niebo [INDEX]
- [ALTITUDE]m
+ Niebo [INDEX]
[ALTITUDE]m
Nieznane
diff --git a/indra/newview/skins/default/xui/pt/panel_region_environment.xml b/indra/newview/skins/default/xui/pt/panel_region_environment.xml
index e38a30e144..d81a82dc4d 100644
--- a/indra/newview/skins/default/xui/pt/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/pt/panel_region_environment.xml
@@ -32,20 +32,17 @@
- Céu [INDEX]
- [ALTITUDE]m
+ Céu [INDEX]
[ALTITUDE]m
Desconhecido
- Céu [INDEX]
- [ALTITUDE]m
+ Céu [INDEX]
[ALTITUDE]m
Desconhecido
- Céu [INDEX]
- [ALTITUDE]m
+ Céu [INDEX]
[ALTITUDE]m
Desconhecido
diff --git a/indra/newview/skins/default/xui/ru/floater_fs_poser.xml b/indra/newview/skins/default/xui/ru/floater_fs_poser.xml
index e54577f5d3..16f5239a1c 100644
--- a/indra/newview/skins/default/xui/ru/floater_fs_poser.xml
+++ b/indra/newview/skins/default/xui/ru/floater_fs_poser.xml
@@ -142,10 +142,6 @@
Живот
Левая грудь
Правая грудь
- Левая основа
- Правая основа
- Левый кончик
- Правый кончик
diff --git a/indra/newview/skins/default/xui/ru/panel_region_environment.xml b/indra/newview/skins/default/xui/ru/panel_region_environment.xml
index 95cb7cea1f..2201dea049 100644
--- a/indra/newview/skins/default/xui/ru/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/ru/panel_region_environment.xml
@@ -72,8 +72,7 @@
- Небо [INDEX]
- [ALTITUDE]м
+ Небо [INDEX]
[ALTITUDE]м
Неизвестно
@@ -82,8 +81,7 @@
- Небо [INDEX]
- [ALTITUDE]м
+ Небо [INDEX]
[ALTITUDE]м
Неизвестно
@@ -92,8 +90,7 @@
- Небо [INDEX]
- [ALTITUDE]м
+ Небо [INDEX]
[ALTITUDE]м
Неизвестно
diff --git a/indra/newview/skins/default/xui/tr/panel_region_environment.xml b/indra/newview/skins/default/xui/tr/panel_region_environment.xml
index d375203cf6..3d80466ed3 100644
--- a/indra/newview/skins/default/xui/tr/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/tr/panel_region_environment.xml
@@ -57,8 +57,7 @@
- Gökyüzü [INDEX]
- [ALTITUDE] m
+ Gökyüzü [INDEX]
[ALTITUDE] m
Bilinmiyor
@@ -67,8 +66,7 @@
- Gökyüzü [INDEX]
- [ALTITUDE] m
+ Gökyüzü [INDEX]
[ALTITUDE] m
Bilinmiyor
@@ -77,8 +75,7 @@
- Gökyüzü [INDEX]
- [ALTITUDE] m
+ Gökyüzü [INDEX]
[ALTITUDE] m
Bilinmiyor
diff --git a/indra/newview/skins/default/xui/zh/floater_fs_poser.xml b/indra/newview/skins/default/xui/zh/floater_fs_poser.xml
index 7e9f9a7360..e6472b9059 100644
--- a/indra/newview/skins/default/xui/zh/floater_fs_poser.xml
+++ b/indra/newview/skins/default/xui/zh/floater_fs_poser.xml
@@ -1,26 +1,23 @@
-
- 身體
- 額頭/眉毛
- 眼睛/眼瞼
- 臉頰/嘴唇
- 左手
- 左臂
- 右手
- 右臂
- 腿
- 尾巴
- 後肢
- 翼
- 耳朵/鼻子
-
-
- 化身整體
- 軀幹
- 胸部
- 頸部
- 頭顱
+ 身體
+ 額頭/眉毛
+ 眼睛/眼瞼
+ 臉頰/嘴唇
+ 左手
+ 左臂
+ 右手
+ 右臂
+ 腿
+ 尾巴
+ 後肢
+ 翼
+ 耳朵/鼻子
+ 化身整體
+ 軀幹
+ 胸部
+ 頸部
+ 頭顱
右眼
左眼
左額頭角
@@ -144,12 +141,8 @@
腹部
左胸肌
右胸肌
- 左耳根
- 右耳根
- 左耳尖
- 右耳尖
-
-
+ 載入姿勢
+ 載入差異
@@ -165,7 +158,7 @@
- 進/出:
+ 前/後:
@@ -208,10 +201,10 @@
-
+
-
+
@@ -220,34 +213,59 @@
觸控板敏感度:
+
+
-
-
- 旋轉:
-
-
+
+
+
+
+
+ 上/下:
+
+
+ 左/右:
+
+
+ 旋轉:
+
+
+
+
+ 位置 X:
+
+
+ 位置 Y:
+
+
+ 位置 Z:
+
+
+ 縮放 X:
+
+
+ 縮放 Y:
+
+
+ 縮放 Z:
+
+
+
-
-
-
+
+
+
-
-
+
+
+
+
-
- 上/下:
-
-
- 左/右:
-
-
- 滾轉:
-
@@ -258,9 +276,8 @@
-
-
+
@@ -269,27 +286,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/indra/newview/skins/default/xui/zh/menu_viewer.xml b/indra/newview/skins/default/xui/zh/menu_viewer.xml
index 97c4989b19..c343f6d514 100644
--- a/indra/newview/skins/default/xui/zh/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/zh/menu_viewer.xml
@@ -511,7 +511,7 @@
-
+
diff --git a/indra/newview/skins/default/xui/zh/panel_progress.xml b/indra/newview/skins/default/xui/zh/panel_progress.xml
index a925df6e98..23cf0d9e95 100644
--- a/indra/newview/skins/default/xui/zh/panel_progress.xml
+++ b/indra/newview/skins/default/xui/zh/panel_progress.xml
@@ -7,7 +7,7 @@
- 正在使用Second Life
+ Second Life 使用
diff --git a/indra/newview/skins/default/xui/zh/panel_region_environment.xml b/indra/newview/skins/default/xui/zh/panel_region_environment.xml
index 71582c1d85..46ad03af60 100644
--- a/indra/newview/skins/default/xui/zh/panel_region_environment.xml
+++ b/indra/newview/skins/default/xui/zh/panel_region_environment.xml
@@ -10,7 +10,7 @@
天空 [INDEX]([ALTITUDE]m)
- 未選擇地塊。 環境設定已停用。
+ 未選擇地塊。環境設定已停用。
跨越區域不提供環境設定。
@@ -74,14 +74,12 @@
[HH]:[MM][AP] ([PRC]%)
-
- 天空[INDEX]
-[ALTITUDE]米
+ 天空[INDEX]
[ALTITUDE]米
未知
@@ -90,8 +88,7 @@
- 天空[INDEX]
-[ALTITUDE]米
+ 天空[INDEX]
[ALTITUDE]米
未知
@@ -100,8 +97,7 @@
- 天空[INDEX]
-[ALTITUDE]米
+ 天空[INDEX]
[ALTITUDE]米
未知
@@ -111,11 +107,6 @@
天空高度
-
-
-
-
-
地面