Poser: Update diff save, Rotation frames, minor enhancements

Signed-off-by: Angeldark Raymaker <dark.angel_raven@yahoo.com.au>
No collab poser
Manip: Integrates better with Poser, now working 'live' like all the other UI controls, feeding back in the same way
Manip: code tidy
Rotation framing: World/Avatar/Screen reference framing for manip & other UI elements
Bone hightlight: with manip off, a debug marker appears a second to guide the eye as you select bones
Diff saves: Better saving of pose-state
master
Angeldark Raymaker 2025-11-25 21:49:26 +00:00
parent 4cb05bb55e
commit 08f8af3cb2
15 changed files with 1538 additions and 469 deletions

View File

@ -8177,6 +8177,17 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserShowBoneHighlights</key>
<map>
<key>Comment</key>
<string>Whether to highlight a bone with the debug beacon on selection from the UI.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>FSPoserStopPosingWhenClosed</key>
<map>
<key>Comment</key>

View File

@ -46,6 +46,8 @@
#include "llinventoryfunctions.h"
#include "lltoolcomp.h"
#include "llloadingindicator.h"
#include "llmutelist.h"
#include "llappviewer.h"
namespace
{
@ -57,6 +59,7 @@ constexpr std::string_view POSE_PRESETS_HANDS_SUBDIRECTORY = "hand_presets";
constexpr char XML_LIST_HEADER_STRING_PREFIX[] = "header_";
constexpr char XML_LIST_TITLE_STRING_PREFIX[] = "title_";
constexpr char XML_JOINT_TRANSFORM_STRING_PREFIX[] = "joint_transform_";
constexpr char XML_JOINT_FRAME_TRANSFORM_PREFIX[] = "joint_frame_";
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_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpadSensitivity";
@ -64,6 +67,7 @@ constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPos
constexpr std::string_view POSER_SAVEEXTERNALFORMAT_SAVE_KEY = "FSPoserSaveExternalFileAlso";
constexpr std::string_view POSER_SAVECONFIRMREQUIRED_SAVE_KEY = "FSPoserOnSaveConfirmOverwrite";
constexpr std::string_view POSER_UNLOCKPELVISINBVH_SAVE_KEY = "FSPoserPelvisUnlockedForBvhSave";
constexpr std::string_view POSER_SHOWBONEHIGHLIGHTS_SAVE_KEY = "FSManipShowJointMarkers";
constexpr char ICON_SAVE_OK[] = "icon_rotation_is_own_work";
constexpr char ICON_SAVE_FAILED[] = "icon_save_failed_button";
@ -82,6 +86,7 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
mCommitCallbackRegistrar.add("Poser.StartStopAnimating", [this](LLUICtrl*, const LLSD&) { onPoseStartStop(); });
mCommitCallbackRegistrar.add("Poser.ToggleLoadSavePanel", [this](LLUICtrl*, const LLSD&) { onToggleLoadSavePanel(); });
mCommitCallbackRegistrar.add("Poser.ToggleVisualManipulators", [this](LLUICtrl*, const LLSD&) { onToggleVisualManipulators(); });
mCommitCallbackRegistrar.add("Poser.ToggleRotationFrame", [this](LLUICtrl* button, const LLSD&) { onToggleRotationFrameButton(button); });
mCommitCallbackRegistrar.add("Poser.UndoLastRotation", [this](LLUICtrl*, const LLSD&) { onUndoLastChange(); });
mCommitCallbackRegistrar.add("Poser.RedoLastRotation", [this](LLUICtrl*, const LLSD&) { onRedoLastChange(); });
@ -205,9 +210,17 @@ bool FSFloaterPoser::postBuild()
mRedoChangeBtn = getChild<LLButton>("button_redo_change");
mUndoChangeBtn = getChild<LLButton>("undo_change");
mSetToTposeButton = getChild<LLButton>("set_t_pose_button");
mBtnJointReset = getChild<LLButton>("poser_joint_reset");
mBtnWorldFrame = getChild<LLButton>("poser_world_frame_toggle");
mBtnAvatarFrame = getChild<LLButton>("poser_avatar_frame_toggle");
mBtnScreenFrame = getChild<LLButton>("poser_screen_frame_toggle");
mJointsParentPnl = getChild<LLPanel>("joints_parent_panel");
mTrackballPnl = getChild<LLPanel>("trackball_panel");
mPositionPnl = getChild<LLPanel>("position_panel");
mMoveTabPnl = getChild<LLPanel>("move_tab_panel");
mTrackballButtonPnl = getChild<LLPanel>("trackball_button_panel");
mPositionRotationPnl = getChild<LLPanel>("positionRotation_panel");
mBodyJointsPnl = getChild<LLPanel>("body_joints_panel");
mFaceJointsPnl = getChild<LLPanel>("face_joints_panel");
@ -269,6 +282,76 @@ void FSFloaterPoser::onFocusLost()
}
}
void FSFloaterPoser::draw()
{
LLFloater::draw();
drawOnHoverJointHint();
}
void FSFloaterPoser::markSelectedJointsToHighlight()
{
bool toolsEnabled = mToggleVisualManipulators->getValue().asBoolean();
if (toolsEnabled)
return;
bool showHighlights = gSavedSettings.getBOOL(POSER_SHOWBONEHIGHLIGHTS_SAVE_KEY);
if (!showHighlights)
return;
auto selectedJoints = getUiSelectedPoserJoints();
if (selectedJoints.size() < 1)
return;
std::string jointName = selectedJoints[0]->jointName();
bool isRightLimb = jointName.find("Right") != std::string::npos;
bool isLeftLimb = jointName.find("Left") != std::string::npos;
if (!(isRightLimb || isLeftLimb))
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
mLastSelectedJoint = selectedJoints[0];
timeFadeStartedMicrosec = gFrameTime;
}
void FSFloaterPoser::drawOnHoverJointHint()
{
if (!mLastSelectedJoint)
return;
constexpr U64 GLOW_TIME_US = 300000;
U64 fadeTimeUs = gFrameTime - timeFadeStartedMicrosec;
if (fadeTimeUs > GLOW_TIME_US)
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
LLJoint* joint = avatar->getJoint(std::string(mLastSelectedJoint->jointName()));
if (!joint)
return;
F32 alphaFade = 1.f * (GLOW_TIME_US - fadeTimeUs) / GLOW_TIME_US;
static LLUIColor mBeaconColor = LLUIColorTable::getInstance()->getColor("AreaSearchBeaconColor");
LLColor4 beaconColour = mBeaconColor.get();
beaconColour.setAlpha(alphaFade);
LLVector3 joint_world_position = joint->getWorldPosition();
static LLCachedControl<S32> beacon_line_width(gSavedSettings, "DebugBeaconLineWidth");
gObjectList.addDebugBeacon(joint_world_position, "", beaconColour, beaconColour, beacon_line_width);
}
void FSFloaterPoser::enableVisualManipulators()
{
if (!gAgentAvatarp || gAgentAvatarp.isNull())
@ -359,6 +442,9 @@ void FSFloaterPoser::onPoseFileSelect()
if (!avatar)
return;
if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar))
return;
bool enableButtons = mPoserAnimator.isPosingAvatar(avatar);
mLoadPosesBtn->setEnabled(enableButtons);
mSavePosesBtn->setEnabled(enableButtons);
@ -409,11 +495,14 @@ void FSFloaterPoser::onClickPoseSave()
mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign());
return;
}
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar))
{
mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign());
return;
}
// if prompts are disabled or file doesn't exist, do the save immediately:
const bool prompt = gSavedSettings.getBOOL(POSER_SAVECONFIRMREQUIRED_SAVE_KEY);
@ -534,7 +623,7 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
record["startFromTeePose"]["value"] = !savingDiff;
if (savingDiff)
mPoserAnimator.savePosingState(avatar, &record);
mPoserAnimator.savePosingState(avatar, false, &record);
LLVector3 rotation, position, scale, zeroVector;
bool baseRotationIsZero;
@ -694,17 +783,19 @@ void FSFloaterPoser::onClickRecaptureSelectedBones()
if (currentlyPosing)
continue;
mPoserAnimator.recaptureJoint(avatar, *item, getJointTranslation(item->jointName()), getJointNegation(item->jointName()));
mPoserAnimator.recaptureJoint(avatar, *item);
}
setSavePosesButtonText(true);
refreshRotationSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
refreshTextHighlightingOnJointScrollLists();
enableOrDisableRedoAndUndoButton();
}
void FSFloaterPoser::updatePosedBones(const std::string& jointName)
void FSFloaterPoser::updatePosedBones(const std::string& jointName, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale)
{
LLVOAvatar *avatar = getUiSelectedAvatar();
if (!avatar)
@ -713,12 +804,19 @@ void FSFloaterPoser::updatePosedBones(const std::string& jointName)
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
bool haveImplicitPermission = havePermissionToAnimateAvatar(avatar); // self & control avatars you own
bool iCanPoseThem = havePermissionToAnimateOtherAvatar(avatar);
if (!haveImplicitPermission && !iCanPoseThem)
return;
const FSPoserAnimator::FSPoserJoint* poserJoint = mPoserAnimator.getPoserJointByName(jointName);
if (!poserJoint)
return;
bool savingToExternal = getSavingToBvh();
mPoserAnimator.recaptureJointAsDelta(avatar, poserJoint, savingToExternal, getUiSelectedBoneDeflectionStyle());
bool savingToExternal = getSavingToBvh();
E_PoserReferenceFrame frame = getReferenceFrame();
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
mPoserAnimator.updateJointFromManip(avatar, poserJoint, savingToExternal, defl, frame, rotation, position, scale);
refreshRotationSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
@ -728,6 +826,24 @@ void FSFloaterPoser::updatePosedBones(const std::string& jointName)
refreshTextHighlightingOnJointScrollLists();
}
LLQuaternion FSFloaterPoser::getManipGimbalRotation(const std::string& jointName)
{
LLVOAvatar *avatar = getUiSelectedAvatar();
if (!avatar)
return LLQuaternion();
if (!mPoserAnimator.isPosingAvatar(avatar))
return LLQuaternion();
const FSPoserAnimator::FSPoserJoint* poserJoint = mPoserAnimator.getPoserJointByName(jointName);
if (!poserJoint)
return LLQuaternion();
E_PoserReferenceFrame frame = getReferenceFrame();
return mPoserAnimator.getManipGimbalRotation(avatar, poserJoint, frame);
}
void FSFloaterPoser::onClickBrowsePoseCache()
{
createUserPoseDirectoryIfNeeded();
@ -930,6 +1046,9 @@ void FSFloaterPoser::timedReload()
if (!avatar)
return;
if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar))
return;
if (loadPoseFromXml(avatar, mLoadPoseTimer->getPosePath(), mLoadPoseTimer->getLoadMethod()))
{
mLoadPoseTimer->completeLoading();
@ -1024,6 +1143,8 @@ void FSFloaterPoser::onClickLoadHandPose(bool isRightHand)
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar))
return;
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
@ -1233,8 +1354,8 @@ bool FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
mPoserAnimator.setRotationIsMirrored(avatar, *poserJoint, mirroredJoint);
}
if (version > 6 && !startFromZeroRot)
loadSuccess = mPoserAnimator.loadPosingState(avatar, pose);
if (version > 6 && !startFromZeroRot && !loadSelective)
loadSuccess = mPoserAnimator.loadPosingState(avatar, true, pose);
}
}
catch ( const std::exception & e )
@ -1326,8 +1447,25 @@ bool FSFloaterPoser::havePermissionToAnimateAvatar(LLVOAvatar *avatar) const
return false;
if (avatar->isSelf())
return true;
if (avatar->isControlAvatar())
return true;
{
LLControlAvatar* control_av = dynamic_cast<LLControlAvatar*>(avatar);
const LLVOVolume* rootVolume = control_av->mRootVolp;
const LLViewerObject* rootEditObject = (rootVolume) ? rootVolume->getRootEdit() : NULL;
if (!rootEditObject)
return false;
return rootEditObject->permYouOwner();
}
return false;
}
bool FSFloaterPoser::havePermissionToAnimateOtherAvatar(LLVOAvatar *avatar) const
{
if (!avatar || avatar->isDead())
return false;
return false;
}
@ -1342,6 +1480,12 @@ void FSFloaterPoser::poseControlsEnable(bool enable)
mLoadPosesBtn->setEnabled(enable);
mSavePosesBtn->setEnabled(enable);
mPoseSaveNameEditor->setEnabled(enable);
mBtnJointReset->setEnabled(enable);
mRedoChangeBtn->setEnabled(enable);
mUndoChangeBtn->setEnabled(enable);
mPositionPnl->setEnabled(enable);
mMoveTabPnl->setEnabled(enable);
mTrackballButtonPnl->setEnabled(enable);
}
void FSFloaterPoser::refreshJointScrollListMembers()
@ -1500,6 +1644,23 @@ void FSFloaterPoser::setRotationChangeButtons(bool togglingMirror, bool toggling
refreshTrackpadCursor();
}
void FSFloaterPoser::onToggleRotationFrameButton(const LLUICtrl* toggleButton)
{
if (!toggleButton)
return;
bool buttonDown = toggleButton->getValue().asBoolean();
if (buttonDown)
{
mBtnAvatarFrame->setValue(toggleButton == mBtnAvatarFrame);
mBtnScreenFrame->setValue(toggleButton == mBtnScreenFrame);
mBtnWorldFrame->setValue(toggleButton == mBtnWorldFrame);
}
FSToolCompPose::getInstance()->setReferenceFrame(getReferenceFrame());
refreshRotationSlidersAndSpinners();
}
void FSFloaterPoser::onUndoLastChange()
{
LLVOAvatar* avatar = getUiSelectedAvatar();
@ -1733,7 +1894,6 @@ LLScrollListCtrl* FSFloaterPoser::getScrollListForTab(LLPanel * tabPanel) const
return mCollisionVolumesScrollList;
}
LL_WARNS() << "Unknown tab panel: " << tabPanel << LL_ENDL;
return nullptr;
}
@ -1787,7 +1947,10 @@ void FSFloaterPoser::updateManipWithFirstSelectedJoint(std::vector<FSPoserAnimat
if (!avatarp)
return;
if (joints.size() >= 1)
bool haveImplicitPermission = havePermissionToAnimateAvatar(avatarp);
bool iCanPoseThem = havePermissionToAnimateOtherAvatar(avatarp);
if ((joints.size() >= 1) && (haveImplicitPermission || iCanPoseThem))
FSToolCompPose::getInstance()->setJoint(avatarp->getJoint(joints[0]->jointName()));
else
FSToolCompPose::getInstance()->setJoint(nullptr);
@ -2073,8 +2236,9 @@ void FSFloaterPoser::setSelectedJointsPosition(F32 x, F32 y, F32 z)
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
LLVector3 vec3 = LLVector3(x, y, z);
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
LLVector3 vec3 = LLVector3(x, y, z);
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
E_PoserReferenceFrame frame = getReferenceFrame();
for (auto item : getUiSelectedPoserJoints())
{
@ -2082,7 +2246,7 @@ void FSFloaterPoser::setSelectedJointsPosition(F32 x, F32 y, F32 z)
if (!currentlyPosingJoint)
continue;
mPoserAnimator.setJointPosition(avatar, item, vec3, defl);
mPoserAnimator.setJointPosition(avatar, item, vec3, frame, defl);
}
}
@ -2097,7 +2261,8 @@ void FSFloaterPoser::setSelectedJointsRotation(const LLVector3& absoluteRot, con
auto selectedJoints = getUiSelectedPoserJoints();
bool savingToExternal = getSavingToBvh();
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
E_BoneDeflectionStyles deflection = getUiSelectedBoneDeflectionStyle();
E_PoserReferenceFrame frame = getReferenceFrame();
for (auto item : selectedJoints)
{
@ -2111,14 +2276,17 @@ void FSFloaterPoser::setSelectedJointsRotation(const LLVector3& absoluteRot, con
bool oppositeJointAlsoSelectedOnUi =
std::find(selectedJoints.begin(), selectedJoints.end(), oppositeJoint) != selectedJoints.end();
bool deflectionDoesOppositeLimbs = !(defl == NONE || defl == DELTAMODE);
bool deflectionDoesOppositeLimbs = !(deflection == NONE || deflection == DELTAMODE);
if (oppositeJointAlsoSelectedOnUi && deflectionDoesOppositeLimbs && item->dontFlipOnMirror())
continue;
}
mPoserAnimator.setJointRotation(avatar, item, absoluteRot, deltaRot, defl,
getJointTranslation(item->jointName()), getJointNegation(item->jointName()), savingToExternal,
getUiSelectedBoneRotationStyle(item->jointName()));
S32 jointNegation = getJointNegation(frame, item->jointName());
E_BoneAxisTranslation translation = getJointTranslation(frame, item->jointName());
E_RotationStyle style = getUiSelectedBoneRotationStyle(item->jointName());
mPoserAnimator.setJointRotation(avatar, item, absoluteRot, deltaRot, deflection, frame, translation, jointNegation,
savingToExternal, style);
}
if (savingToExternal)
@ -2134,8 +2302,9 @@ void FSFloaterPoser::setSelectedJointsScale(F32 x, F32 y, F32 z)
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
LLVector3 vec3 = LLVector3(x, y, z);
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
LLVector3 vec3 = LLVector3(x, y, z);
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
E_PoserReferenceFrame frame = getReferenceFrame();
for (auto item : getUiSelectedPoserJoints())
{
@ -2143,7 +2312,7 @@ void FSFloaterPoser::setSelectedJointsScale(F32 x, F32 y, F32 z)
if (!currentlyPosingJoint)
continue;
mPoserAnimator.setJointScale(avatar, item, vec3, defl);
mPoserAnimator.setJointScale(avatar, item, vec3, frame, defl);
}
}
@ -2161,8 +2330,10 @@ LLVector3 FSFloaterPoser::getRotationOfFirstSelectedJoint() const
if (!mPoserAnimator.isPosingAvatar(avatar))
return rotation;
rotation = mPoserAnimator.getJointRotation(avatar, *selectedJoints.front(), getJointTranslation(selectedJoints.front()->jointName()),
getJointNegation(selectedJoints.front()->jointName()));
E_PoserReferenceFrame frame = getReferenceFrame();
rotation = mPoserAnimator.getJointRotation(avatar, *selectedJoints.front(), getJointTranslation(frame, selectedJoints.front()->jointName()),
getJointNegation(frame, selectedJoints.front()->jointName()));
return rotation;
}
@ -2210,18 +2381,25 @@ void FSFloaterPoser::onJointTabSelect()
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
refreshScaleSlidersAndSpinners();
markSelectedJointsToHighlight();
}
E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& jointName) const
E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(E_PoserReferenceFrame frame, const std::string& jointName) const
{
if (jointName.empty())
return SWAP_NOTHING;
bool hasTransformParameter = hasString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName);
std::string paramName;
if (frame == POSER_FRAME_BONE)
paramName = XML_JOINT_TRANSFORM_STRING_PREFIX + jointName;
else
paramName = XML_JOINT_FRAME_TRANSFORM_PREFIX + jointName;
bool hasTransformParameter = hasString(paramName);
if (!hasTransformParameter)
return SWAP_NOTHING;
std::string paramValue = getString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName);
std::string paramValue = getString(paramName);
if (strstr(paramValue.c_str(), "SWAP_YAW_AND_ROLL"))
return SWAP_YAW_AND_ROLL;
@ -2237,18 +2415,24 @@ E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& joi
return SWAP_NOTHING;
}
S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const
S32 FSFloaterPoser::getJointNegation(E_PoserReferenceFrame frame, const std::string& jointName) const
{
S32 result = NEGATE_NOTHING;
if (jointName.empty())
return result;
bool hasTransformParameter = hasString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName);
if (!hasTransformParameter)
std::string paramName;
if (frame == POSER_FRAME_BONE)
paramName = XML_JOINT_TRANSFORM_STRING_PREFIX + jointName;
else
paramName = XML_JOINT_FRAME_TRANSFORM_PREFIX + jointName;
bool hasNegationParameter = hasString(paramName);
if (!hasNegationParameter)
return result;
std::string paramValue = getString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName);
std::string paramValue = getString(paramName);
if (strstr(paramValue.c_str(), "NEGATE_YAW"))
result |= NEGATE_YAW;
@ -2262,6 +2446,23 @@ S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const
return result;
}
E_PoserReferenceFrame FSFloaterPoser::getReferenceFrame() const
{
bool toggleButtonValue = mBtnScreenFrame->getValue().asBoolean();
if (toggleButtonValue)
return POSER_FRAME_CAMERA;
toggleButtonValue = mBtnAvatarFrame->getValue().asBoolean();
if (toggleButtonValue)
return POSER_FRAME_AVATAR;
toggleButtonValue = mBtnWorldFrame->getValue().asBoolean();
if (toggleButtonValue)
return POSER_FRAME_WORLD;
return POSER_FRAME_BONE;
}
/// <summary>
/// An event handler for selecting an avatar or animesh on the POSES_AVATAR_SCROLL_LIST_NAME.
/// In general this will refresh the views for joints or their proxies, and (dis/en)able elements of the view.
@ -2269,16 +2470,26 @@ S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const
void FSFloaterPoser::onAvatarSelect()
{
LLVOAvatar* avatar = getUiSelectedAvatar();
if(avatar)
{
if (!avatar)
return;
bool isSelf = avatar->isSelf();
bool haveImplicitPermission = havePermissionToAnimateAvatar(avatar); // self & control avatars you own
bool haveExplicitPermission = havePermissionToAnimateOtherAvatar(avatar); // as permissions allow
if (haveImplicitPermission || haveExplicitPermission)
FSToolCompPose::getInstance()->setAvatar(avatar);
}
mStartStopPosingBtn->setEnabled(couldAnimateAvatar(avatar));
else
FSToolCompPose::getInstance()->setAvatar(nullptr);
bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar);
mStartStopPosingBtn->setEnabled(haveImplicitPermission);
mStartStopPosingBtn->setValue(arePosingSelected);
mSetToTposeButton->setEnabled(arePosingSelected);
poseControlsEnable(arePosingSelected);
mSetToTposeButton->setEnabled(haveImplicitPermission && arePosingSelected);
poseControlsEnable(arePosingSelected && haveImplicitPermission);
refreshTextHighlightingOnAvatarScrollList();
refreshTextHighlightingOnJointScrollLists();
onJointTabSelect();
@ -2292,7 +2503,16 @@ uuid_vec_t FSFloaterPoser::getNearbyAvatarsAndAnimeshes() const
for (LLCharacter* character : LLCharacter::sInstances)
{
LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(character);
if (!havePermissionToAnimateAvatar(avatar))
if (!avatarIsNearbyMe(avatar))
continue;
bool isMuted = LLMuteList::getInstance()->isMuted(avatar->getID());
if (isMuted)
continue;
bool isSelfOrCtrl = avatar->isControlAvatar() || avatar->isSelf();
if (!isSelfOrCtrl)
continue;
avatar_ids.emplace_back(character->getID());
@ -2301,6 +2521,16 @@ uuid_vec_t FSFloaterPoser::getNearbyAvatarsAndAnimeshes() const
return avatar_ids;
}
bool FSFloaterPoser::avatarIsNearbyMe(LLCharacter* character) const
{
if (!gAgentAvatarp || gAgentAvatarp.isNull() || !character)
return false;
LLVector3 separationVector = character->getCharacterPosition() - gAgentAvatarp->getCharacterPosition();
return separationVector.magVec() < 50.f;
}
uuid_vec_t FSFloaterPoser::getCurrentlyListedAvatarsAndAnimeshes() const
{
uuid_vec_t avatar_ids;
@ -2351,10 +2581,6 @@ void FSFloaterPoser::onAvatarsRefresh()
mAvatarSelectionScrollList->deleteSingleItem(indexToRemove);
}
std::string iconCatagoryName = "Inv_BodyShape";
if (hasString("icon_category"))
iconCatagoryName = getString("icon_category");
std::string iconObjectName = "Inv_Object";
if (hasString("icon_object"))
iconObjectName = getString("icon_object");
@ -2377,10 +2603,17 @@ void FSFloaterPoser::onAvatarsRefresh()
if (!LLAvatarNameCache::get(uuid, &av_name))
continue;
bool isMuted = LLMuteList::getInstance()->isMuted(uuid);
if (isMuted)
continue;
if (!avatar->isSelf())
continue;
LLSD row;
row["columns"][COL_ICON]["column"] = "icon";
row["columns"][COL_ICON]["type"] = "icon";
row["columns"][COL_ICON]["value"] = iconCatagoryName;
row["columns"][COL_ICON]["value"] = getIconNameForAvatar(avatar);
row["columns"][COL_NAME]["column"] = "name";
row["columns"][COL_NAME]["value"] = av_name.getDisplayName();
row["columns"][COL_UUID]["column"] = "uuid";
@ -2423,6 +2656,21 @@ void FSFloaterPoser::onAvatarsRefresh()
refreshTextHighlightingOnAvatarScrollList();
}
std::string FSFloaterPoser::getIconNameForAvatar(LLVOAvatar* avatar)
{
std::string iconName = "Inv_BodyShape";
if (hasString("icon_category"))
iconName = getString("icon_category");
if (!avatar)
return iconName;
if (avatar->isControlAvatar() && hasString("icon_object"))
return getString("icon_object");
return iconName;
}
std::string FSFloaterPoser::getControlAvatarName(const LLControlAvatar* avatar)
{
if (!avatar)
@ -2456,6 +2704,8 @@ void FSFloaterPoser::refreshTextHighlightingOnAvatarScrollList()
LLUUID selectedAvatarId = cell->getValue().asUUID();
LLVOAvatar* listAvatar = getAvatarByUuid(selectedAvatarId);
((LLScrollListText*)listItem->getColumn(COL_ICON))->setValue(getIconNameForAvatar(listAvatar));
if (mPoserAnimator.isPosingAvatar(listAvatar))
((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD);
else
@ -2530,7 +2780,7 @@ std::string FSFloaterPoser::getScrollListIconForJoint(LLVOAvatar* avatar, FSPose
return tryGetString("icon_rotation_bvh_unlocked");
}
std::string FSFloaterPoser::tryGetString(std::string name)
std::string FSFloaterPoser::tryGetString(std::string_view name)
{
if (name.empty())
return "";

View File

@ -31,6 +31,7 @@
#include "llfloater.h"
#include "lltoolmgr.h"
#include "fsposeranimator.h"
#include "fsmaniprotatejoint.h"
class FSVirtualTrackpad;
class LLButton;
@ -80,7 +81,8 @@ class FSFloaterPoser : public LLFloater, public LLEditMenuHandler
friend class LLFloaterReg;
FSFloaterPoser(const LLSD &key);
public:
void updatePosedBones(const std::string& jointName);
void updatePosedBones(const std::string& jointName, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale);
LLQuaternion getManipGimbalRotation(const std::string& jointName);
void selectJointByName(const std::string& jointName);
void undo() override { onUndoLastChange(); };
bool canUndo() const override { return true; }
@ -94,6 +96,8 @@ public:
void onClose(bool app_quitting) override;
void onFocusReceived() override;
void onFocusLost() override;
virtual void draw() override;
/// <summary>
/// Refreshes the supplied pose list from the supplued subdirectory.
/// </summary>
@ -181,6 +185,13 @@ public:
/// <returns>A the collection of UUIDs for nearby avatars.</returns>
uuid_vec_t getNearbyAvatarsAndAnimeshes() const;
/// <summary>
/// Gets whether the supplied character is within chat range of gAgentAvatar.
/// </summary>
/// <param name="character">The character to query whether nearby.</param>
/// <returns>True if the supplied character is within chat range, otherwise false.</returns>
bool avatarIsNearbyMe(LLCharacter* character) const;
/// <summary>
/// Gets a collection of UUIDs for avatars currently being presented on the UI.
/// </summary>
@ -242,12 +253,17 @@ public:
void enableVisualManipulators();
void disableVisualManipulators();
// Visual cue for which bone is under the mouse-cursor
void drawOnHoverJointHint();
void markSelectedJointsToHighlight();
// UI Event Handlers
void onAvatarsRefresh();
void onAvatarSelect();
void onJointTabSelect();
void onToggleMirrorChange();
void onToggleSympatheticChange();
void onToggleRotationFrameButton(const LLUICtrl* toggleButton);
void onToggleVisualManipulators();
void setRotationChangeButtons(bool mirror, bool sympathetic);
void onUndoLastChange();
@ -280,6 +296,14 @@ public:
void refreshTrackpadCursor();
void enableOrDisableRedoAndUndoButton();
/// <summary>
/// Determines if we have permission to animate the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar to animate.</param>
/// <returns>True if we have permission to animate, otherwise false.</returns>
bool havePermissionToAnimateOtherAvatar(LLVOAvatar* avatar) const;
/// <summary>
/// Determines if we have permission to animate the supplied avatar.
/// </summary>
@ -306,6 +330,7 @@ public:
/// This facilitates 'conceptual' conversion of Euler frame to up/down, left/right and roll and is rather subjective.
/// Thus, many of these 'conversions' are backed by values in the XML.
/// </summary>
/// <param name="frame">The reference frame for the change.</param>
/// <param name="jointName">The well-known name of the joint, eg: mChest.</param>
/// <returns>The axial translation so the oily angles make better sense in terms of up/down/left/right/roll.</returns>
/// <remarks>
@ -313,14 +338,21 @@ public:
/// No the translation isn't untangling all of that, it's not needed until it is.
/// We're not landing on Mars with this code, just offering a user reasonable thumb-twiddlings.
/// </remarks>
E_BoneAxisTranslation getJointTranslation(const std::string& jointName) const;
E_BoneAxisTranslation getJointTranslation(E_PoserReferenceFrame frame, const std::string& jointName) const;
/// <summary>
/// Gets the collection of E_BoneAxisNegation values for the supplied joint.
/// </summary>
/// <param name="frame">The reference frame for the change.</param>
/// <param name="jointName">The name of the joind to get the axis transformation for.</param>
/// <returns>The kind of axis transformation to perform.</returns>
S32 getJointNegation(const std::string& jointName) const;
S32 getJointNegation(E_PoserReferenceFrame frame, const std::string& jointName) const;
/// <summary>
/// Gets the reference frame for the rotation/position/scale change.
/// </summary>
/// <returns>The reference frame for the change.</returns>
E_PoserReferenceFrame getReferenceFrame() const;
/// <summary>
/// Gets the axial translation required for joints when saving to BVH.
@ -336,6 +368,13 @@ public:
/// </summary>
void refreshTextHighlightingOnAvatarScrollList();
/// <summary>
/// Gets an appropriate icon for the supplied avatar, based on sharing permission.
/// </summary>
/// <param name="avatar">The avatar to get an icon for.</param>
/// <returns>A string with the name of an icon.</returns>
std::string getIconNameForAvatar(LLVOAvatar* avatar);
/// <summary>
/// Refreshes the text on all joints scroll lists based on their state.
/// </summary>
@ -367,7 +406,7 @@ public:
/// </summary>
/// <param name="name">The name of the string.</param>
/// <returns>The named string, if it exists, otherwise an empty string.</returns>
std::string tryGetString(std::string name);
std::string tryGetString(std::string_view name);
/// <summary>
/// Gets the name of an item from the supplied object ID.
@ -450,10 +489,12 @@ public:
/// </remarks>
static F32 clipRange(F32 value);
LLToolset* mLastToolset{ nullptr };
LLTool* mJointRotTool{ nullptr };
LLVector3 mLastSliderRotation;
LLToolset* mLastToolset{ nullptr };
LLTool* mJointRotTool{ nullptr };
LLVector3 mLastSliderRotation;
FSPoserAnimator::FSPoserJoint* mLastSelectedJoint{ nullptr };
U64 timeFadeStartedMicrosec;
FSVirtualTrackpad* mAvatarTrackball{ nullptr };
@ -502,6 +543,10 @@ public:
LLButton* mUndoChangeBtn{ nullptr };
LLButton* mSetToTposeButton{ nullptr };
LLButton* mBtnJointRotate{ nullptr };
LLButton* mBtnJointReset{ nullptr };
LLButton* mBtnWorldFrame{ nullptr };
LLButton* mBtnAvatarFrame{ nullptr };
LLButton* mBtnScreenFrame{ nullptr };
LLLineEditor* mPoseSaveNameEditor{ nullptr };
@ -516,6 +561,9 @@ public:
LLPanel* mMiscJointsPnl{ nullptr };
LLPanel* mCollisionVolumesPnl{ nullptr };
LLPanel* mPosesLoadSavePnl{ nullptr };
LLPanel* mPositionPnl{ nullptr };
LLPanel* mMoveTabPnl{ nullptr };
LLPanel* mTrackballButtonPnl{ nullptr };
LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr };
LLCheckBoxCtrl* mUnlockPelvisInBvhSaveCbx{ nullptr };

View File

@ -47,6 +47,7 @@ FSJointPose::FSJointPose(LLJoint* joint, U32 usage, bool isCollisionVolume)
mJointName = joint->getName();
mIsCollisionVolume = isCollisionVolume;
mJointNumber = joint->getJointNum();
mCurrentState = FSJointState(joint);
}
@ -64,6 +65,8 @@ void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
if (zeroBase)
zeroBaseRotation(true);
else
mCurrentState.mUserSpecifiedBaseZero = false;
mCurrentState.mRotation.set(rot);
mCurrentState.mLastChangeWasRotational = true;
@ -98,6 +101,8 @@ void FSJointPose::resetJoint()
void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo)
{
mModifiedThisSession = true;
auto timeIntervalSinceLastChange = std::chrono::system_clock::now() - mTimeLastUpdatedCurrentState;
mTimeLastUpdatedCurrentState = std::chrono::system_clock::now();
@ -151,27 +156,28 @@ FSJointPose::FSJointState FSJointPose::redoLastStateChange(FSJointState thingToS
void FSJointPose::recaptureJoint()
{
if (mIsCollisionVolume)
return;
LLJoint* joint = mJointState->getJoint();
if (!joint)
return;
addStateToUndo(FSJointState(mCurrentState));
if (mIsCollisionVolume)
{
mCurrentState.mPosition.set(LLVector3::zero);
mCurrentState.mScale.set(LLVector3::zero);
}
mCurrentState = FSJointState(joint);
mCurrentState.mLastChangeWasRotational = true;
}
LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase)
LLQuaternion FSJointPose::updateJointAsDelta(bool zeroBase, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale)
{
LLJoint* joint = mJointState->getJoint();
if (!joint)
return LLQuaternion::DEFAULT;
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mLastChangeWasRotational = true;
return mCurrentState.updateFromJoint(joint, zeroBase);
return mCurrentState.updateFromJointProperties(zeroBase, rotation, position, scale);
}
void FSJointPose::setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority)
@ -247,6 +253,7 @@ void FSJointPose::reflectRotation()
if (mIsCollisionVolume)
return;
mModifiedThisSession = true;
mCurrentState.reflectRotation();
}

View File

@ -97,6 +97,8 @@ class FSJointPose
/// 'Public rotation' is the amount of rotation the user has added to the initial state.
/// Public rotation is what a user may save to an external format (such as BVH).
/// This distinguishes 'private' rotation, which is the state inherited from something like a pose in-world.
/// If zeroBase is true, we treat rotations as if in BVH mode: user work.
/// If zeroBase is false, we treat as NOT BVH: some existing pose and user work.
/// </remarks>
void setPublicRotation(bool zeroBase, const LLQuaternion& rot);
@ -173,8 +175,11 @@ class FSJointPose
/// Recalculates the delta reltive to the base for a new rotation.
/// </summary>
/// <param name="zeroBase">Whether to zero the base rotation on setting the supplied rotation.</param>
/// <param name="rotation">The rotation of the supplied joint.</param>
/// <param name="position">The position of the supplied joint.</param>
/// <param name="scale">The scale of the supplied joint.</param>
/// <returns>The rotation of the public difference between before and after recapture.</returns>
LLQuaternion recaptureJointAsDelta(bool zeroBase);
LLQuaternion updateJointAsDelta(bool zeroBase, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale);
/// <summary>
/// Sets the base rotation to the supplied rotation if the supplied priority is appropriate.
@ -253,6 +258,18 @@ class FSJointPose
/// </summary>
LLPointer<LLJointState> getJointState() const { return mJointState; }
/// <summary>
/// Gets whether this joint has been modified this session.
/// </summary>
/// <returns>True if the joint has been changed at all, otherwise false.</returns>
bool getJointModified() const { return mModifiedThisSession; }
/// <summary>
/// Gets the number of the joint represented by this.
/// </summary>
/// <returns>The joint number, derived from LLjoint.</returns>
S32 getJointNumber() const { return mJointNumber; }
class FSJointState
{
public:
@ -326,15 +343,12 @@ class FSJointPose
joint->setScale(mBaseScale);
}
LLQuaternion updateFromJoint(LLJoint* joint, bool zeroBase)
LLQuaternion updateFromJointProperties(bool zeroBase, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale)
{
if (!joint)
return LLQuaternion::DEFAULT;
LLQuaternion initalPublicRot = mRotation;
LLQuaternion invRot = mBaseRotation;
invRot.conjugate();
LLQuaternion newPublicRot = joint->getRotation() * invRot;
LLQuaternion newPublicRot = rotation * invRot;
if (zeroBase)
{
@ -343,8 +357,8 @@ class FSJointPose
}
mRotation.set(newPublicRot);
mPosition.set(joint->getPosition() - mBasePosition);
mScale.set(joint->getScale() - mBaseScale);
mPosition.set(position - mBasePosition);
mScale.set(scale - mBaseScale);
return newPublicRot *= ~initalPublicRot;
}
@ -450,6 +464,13 @@ class FSJointPose
/// </summary>
bool mIsCollisionVolume{ false };
S32 mJointNumber = -1;
/// <summary>
/// Whether this joint has ever been changed by poser.
/// </summary>
bool mModifiedThisSession{ false };
std::deque<FSJointState> mLastSetJointStates;
size_t mUndoneJointStatesIndex = 0;
std::chrono::system_clock::time_point mTimeLastUpdatedCurrentState = std::chrono::system_clock::now();

View File

@ -25,11 +25,8 @@
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fsmaniprotatejoint.h"
// library includes
#include "llviewerprecompiledheaders.h"
#include "fsmaniprotatejoint.h"
#include "llmath.h"
#include "llgl.h"
#include "llrender.h"
@ -37,11 +34,10 @@
#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 "llagent.h" // for gAgent, etc.
#include "llagentcamera.h"
#include "llappviewer.h"
#include "llcontrol.h"
@ -173,15 +169,18 @@ static void renderStaticSphere(const LLVector3& joint_world_position, const LLCo
}
}
bool FSManipRotateJoint::isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera, F32& outRayDistanceFromCenter) const
{
// LL_INFOS("FSManipRotateJoint") << "Checking mouse("<< mouseX << "," << mouseY << ") over joint at: " << jointWorldPos << LL_ENDL;
auto joint_center = gAgent.getPosGlobalFromAgent( jointWorldPos );
if (!mJoint || !mAvatar)
return false;
if (mAvatar->isDead() || !mAvatar->isFullyLoaded())
return false;
auto joint_center = mAvatar->getPosGlobalFromAgent(jointWorldPos);
// centre in *agent* space
LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(joint_center);
LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(joint_center);
LLVector3 ray_pt, ray_dir;
LLManipRotate::mouseToRay(mouseX, mouseY, &ray_pt, &ray_dir);
@ -201,9 +200,9 @@ bool FSManipRotateJoint::isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector
outDistanceFromCamera = proj_len - offset; // distance along the ray to the front intersection
outRayDistanceFromCenter = offset;
return true;
}
}
}
return (false);
return false;
}
//static
@ -232,7 +231,6 @@ const std::vector<std::string_view> FSManipRotateJoint::sSelectableJoints =
{ "mHipRight" },
{ "mKneeRight" },
{ "mAnkleRight" },
};
const std::unordered_map<FSManipRotateJoint::e_manip_part, FSManipRotateJoint::RingRenderParams> FSManipRotateJoint::sRingParams =
@ -241,6 +239,7 @@ const std::unordered_map<FSManipRotateJoint::e_manip_part, FSManipRotateJoint::R
{ 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 bones natural coordinate system.
@ -268,7 +267,7 @@ 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);
LLVector3 joint_local_pos(0.f,0.f,0.f);
// Transform the local endpoint (mEnd) into world space.
LLVector3 localEnd = mJoint->getEnd();
@ -331,13 +330,14 @@ void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY)
mHighlightedJoint = nullptr; // reset the highlighted joint
// Iterate through the avatar's joint map.
F32 nearest_hit_distance = 0.f;
F32 nearest_ray_distance = 0.f;
LLJoint * nearest_joint = nullptr;
for ( const auto& entry : getSelectableJoints())
F32 nearest_hit_distance = 0.f;
F32 nearest_ray_distance = 0.f;
LLJoint* nearest_joint = nullptr;
LLCachedControl<F32> target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.03f);
for (const auto& entry : getSelectableJoints())
{
LLJoint* joint = mAvatar->getJoint(std::string(entry));
LLJoint* joint = mAvatar->getJoint(std::string(entry));
if (!joint)
continue;
@ -347,23 +347,22 @@ void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY)
// Retrieve the joint's world position (in agent space).
LLVector3 jointWorldPos = joint->getWorldPosition();
LLCachedControl<F32> target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.03f);
F32 distance_from_camera;
F32 distance_from_joint;
if (isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera, distance_from_joint) == true)
if (!isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera, distance_from_joint))
continue;
// we want to highlight the closest
// If there is no joint or this joint is a closer hit than the previous one
if (!nearest_joint || nearest_ray_distance > distance_from_camera ||
(nearest_ray_distance == distance_from_camera && nearest_hit_distance > distance_from_joint))
{
// we want to highlight the closest
// If there is no joint or
// this joint is a closer hit than the previous one
if (!nearest_joint || nearest_ray_distance > distance_from_camera ||
(nearest_ray_distance == distance_from_camera && nearest_hit_distance > distance_from_joint))
{
nearest_joint = joint;
nearest_hit_distance = distance_from_joint;
nearest_ray_distance = distance_from_camera;
}
nearest_joint = joint;
nearest_hit_distance = distance_from_joint;
nearest_ray_distance = distance_from_camera;
}
}
mHighlightedJoint = nearest_joint;
}
@ -376,19 +375,26 @@ FSManipRotateJoint::FSManipRotateJoint(LLToolComposite* composite)
void FSManipRotateJoint::setJoint(LLJoint* joint)
{
mJoint = joint;
if (!mJoint)
return;
// Save initial rotation as baseline for delta rotation
if (mJoint)
{
mSavedJointRot = mJoint->getWorldRotation();
mBoneAxes = computeBoneAxes();
mNaturalAlignmentQuat = computeAlignmentQuat(mBoneAxes);
}
mSavedJointRot = getSelectedJointWorldRotation();
mBoneAxes = computeBoneAxes();
mNaturalAlignmentQuat = computeAlignmentQuat(mBoneAxes);
}
void FSManipRotateJoint::setAvatar(LLVOAvatar* avatar)
{
mAvatar = avatar;
if (!avatar)
mJoint = nullptr;
if (!mJoint)
return;
setJoint(avatar->getJoint(mJoint->getJointNum()));
}
/**
@ -403,7 +409,7 @@ void FSManipRotateJoint::handleSelect()
// Not entirely sure this is needed in the current implementation.
if (mJoint)
{
mSavedJointRot = mJoint->getWorldRotation();
mSavedJointRot = getSelectedJointWorldRotation();
}
}
@ -422,15 +428,12 @@ void FSManipRotateJoint::handleSelect()
*/
bool FSManipRotateJoint::updateVisiblity()
{
if (!mJoint)
{
// No joint to manipulate, not visible
if (!isAvatarJointSafeToUse())
return false;
}
if (!hasMouseCapture())
{
mRotationCenter = gAgent.getPosGlobalFromAgent( mJoint->getWorldPosition() );
mRotationCenter = mAvatar->getPosGlobalFromAgent(mJoint->getWorldPosition());
mCamEdgeOn = false;
}
@ -439,7 +442,7 @@ bool FSManipRotateJoint::updateVisiblity()
//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 LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); // Convert from world/agent to global
const auto * viewer_camera = LLViewerCamera::getInstance();
visible = viewer_camera->projectPosAgentToScreen(agent_space_center, mCenterScreen );
@ -473,7 +476,6 @@ bool FSManipRotateJoint::updateVisiblity()
return visible;
}
/**
* @brief Updates the scale of a specific manipulator part.
*
@ -500,21 +502,29 @@ void FSManipRotateJoint::renderRingPass(const RingRenderParams& params, float ra
{
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]);
}
// 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;
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);
@ -552,7 +562,7 @@ void FSManipRotateJoint::renderManipulatorRings(const LLVector3& agent_space_cen
for (int pass = 0; pass < 2; ++pass)
{
if( mManipPart == LL_NO_PART || mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL)
if (mManipPart == LL_NO_PART || mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL)
{
renderCenterSphere( mRadiusMeters);
for (auto& ring_params : sRingParams)
@ -565,16 +575,17 @@ void FSManipRotateJoint::renderManipulatorRings(const LLVector3& agent_space_cen
}
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);
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);
gGL.multMatrix((GLfloat*)inv_rot_mat.mMatrix);
renderCenterCircle( mRadiusMeters*1.2f, roll_color, roll_color );
}
gGL.popMatrix();
@ -619,7 +630,7 @@ void FSManipRotateJoint::renderCenterCircle(const F32 radius, const LLColor4& no
LLVector3 rotationAxis = defaultNormal % targetNormal; // Cross product.
F32 dot = defaultNormal * targetNormal; // Dot product.
F32 angle = acosf(dot) * RAD_TO_DEG; // Convert to degrees.
F32 angle = acosf(dot) * RAD_TO_DEG; // Convert to degrees.
if (rotationAxis.magVec() > 0.001f)
{
@ -664,16 +675,15 @@ void FSManipRotateJoint::renderCenterSphere(const F32 radius, const LLColor4& no
// 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.flush();
}
gGL.popMatrix();
}
/**
* @brief Renders the joint rotation manipulator and associated visual elements.
*
@ -697,72 +707,65 @@ void FSManipRotateJoint::renderCenterSphere(const F32 radius, const LLColor4& no
*/
void FSManipRotateJoint::render()
{
// Early-out if no joint or avatar.
// Needs more something: if they log out while dots on them, asplode
if (!mJoint || !mAvatar || mAvatar->isDead())
{
if (!isAvatarJointSafeToUse())
return;
}
// update visibility and rotation center.
bool activeJointVisible = updateVisiblity();
// Setup GL state.
LLGLSUIDefault gls_ui;
gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep);
LLGLDepthTest gls_depth(GL_TRUE);
LLGLEnable gl_blend(GL_BLEND);
// Iterate through the avatar's joint map.
// If a joint other than the currently selected is highlighted, render a pulsing sphere.
// otherwise a small static sphere
LLCachedControl<bool> show_joint_markers(gSavedSettings, "FSManipShowJointMarkers", true);
LLVector3 jointLocation;
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();
if( joint == mHighlightedJoint && joint != mJoint )
if (joint == mJoint)
continue;
jointLocation = joint->getWorldPosition();
if (joint == mHighlightedJoint)
{
renderPulsingSphere(joint->getWorldPosition());
}
else if( joint != mJoint )
{
// Render a static sphere for the joint being manipulated.
LLCachedControl<bool> show_joint_markers(gSavedSettings, "FSManipShowJointMarkers", true);
if(show_joint_markers)
{
renderStaticSphere(joint->getWorldPosition(), LLColor4(1.f, 0.5f, 0.f, 0.5f), 0.01f);
}
renderPulsingSphere(jointLocation);
continue;
}
if (show_joint_markers)
renderStaticSphere(jointLocation, LLColor4(1.f, 0.5f, 0.f, 0.5f), 0.01f);
}
if (!activeJointVisible)
{
return;
}
// Update joint world matrices.
mJoint->updateWorldMatrixParent();
mJoint->updateWorldMatrix();
const LLQuaternion joint_world_rotation = mJoint->getWorldRotation();
const LLQuaternion joint_world_rotation = getSelectedJointWorldRotation();
const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT;
LLQuaternion currentLocalRot = mJoint->getRotation();
LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot;
// Compute the final world alignment:
LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot;
const LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter);
const LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter);
LLCachedControl<bool> use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true);
LLQuaternion active_rotation = use_natural_direction? final_world_alignment : joint_world_rotation;
LLCachedControl<bool> use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true);
LLQuaternion active_rotation = use_natural_direction ? final_world_alignment : joint_world_rotation;
active_rotation.normalize();
// Render the manipulator rings in a separate function.
gGL.matrixMode(LLRender::MM_MODELVIEW);
renderAxes(agent_space_center, mRadiusMeters * 1.5f, active_rotation);
@ -779,13 +782,13 @@ void FSManipRotateJoint::renderAxes(const LLVector3& agent_space_center, F32 siz
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);
@ -875,9 +878,7 @@ void FSManipRotateJoint::renderNameXYZ(const LLQuaternion& rot)
S32 vertical_offset = window_center_y - VERTICAL_OFFSET;
LLVector3 euler_angles;
rot.getEulerAngles(&euler_angles.mV[0],
&euler_angles.mV[1],
&euler_angles.mV[2]);
rot.getEulerAngles(&euler_angles.mV[0], &euler_angles.mV[1], &euler_angles.mV[2]);
euler_angles *= RAD_TO_DEG;
for (S32 i = 0; i < 3; ++i)
{
@ -925,15 +926,6 @@ void FSManipRotateJoint::renderNameXYZ(const LLQuaternion& rot)
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%c", getManipPartString(mManipPart).c_str(), mCamEdgeOn?'*':' '), window_center_x + 30.f, base_y, LLColor4(1.f, 1.f, .1f, 1.f));
if (mManipPart != LL_NO_PART)
{
LL_INFOS("FSManipRotateJoint") << "Joint: " << mJoint->getName()
<< ", Manip: " << getManipPartString(mManipPart)
<< ", Quaternion: " << rot
<< ", Euler Angles: " << mLastEuler
<< ", Delta Angle: " << mLastAngle * RAD_TO_DEG
<< LL_ENDL;
}
}
gGL.popMatrix();
@ -954,48 +946,46 @@ void FSManipRotateJoint::renderActiveRing( F32 radius, F32 width, const LLColor4
}
}
// -------------------------------------
// 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)
{
if (!isAvatarJointSafeToUse())
return false;
}
// Highlight the manipulator as before.
highlightManipulators(x, y);
if (mHighlightedPart != LL_NO_PART)
if (mHighlightedPart == LL_NO_PART)
return false;
mManipPart = (EManipPart)mHighlightedPart;
// Get the joint's center in agent space.
LLVector3 agent_space_center = mAvatar->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())
{
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;
}
// 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 = getSelectedJointWorldRotation();
// Capture the mouse for dragging.
setMouseCapture(true);
return true;
}
return false;
}
@ -1017,24 +1007,27 @@ bool FSManipRotateJoint::handleMouseDown(S32 x, S32 y, MASK mask)
*/
bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask)
{
auto * poser = dynamic_cast<FSFloaterPoser*>(LLFloaterReg::findInstance("fs_poser"));
// For joint manipulation, require both a valid joint and avatar.
if (!isAvatarJointSafeToUse())
return false;
auto* poser = dynamic_cast<FSFloaterPoser*>(LLFloaterReg::findInstance("fs_poser"));
if (!poser)
return false;
// 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 || mAvatar->isDead() || !poser)
{
return false;
}
poser->setFocus(true);
S32 hit_part = mHighlightedPart;
// Save the joints current world rotation as the basis for the drag.
mSavedJointRot = mJoint->getWorldRotation();
mSavedJointRot = getSelectedJointWorldRotation();
mLastSetRotation.set(LLQuaternion());
mManipPart = (EManipPart)hit_part;
// Convert rotation center from global to agent space.
LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter);
LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter);
// based on mManipPArt (set in highlightmanipulators). decide whether we are constrained or not in the rotation
if (mManipPart == LL_ROT_GENERAL)
@ -1098,35 +1091,27 @@ bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask)
// 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<FSFloaterPoser*>(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 && mJoint)
{
poser->updatePosedBones(mJoint->getName());
}
// Release mouse
{
setMouseCapture(false);
mManipPart = LL_NO_PART;
mLastAngle = 0.0f;
mLastAngle = 0.0f;
mCamEdgeOn = false;
return true;
}
else if(mHighlightedJoint)
else if (mHighlightedJoint)
{
auto* poser = dynamic_cast<FSFloaterPoser*>(LLFloaterReg::findInstance("fs_poser"));
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
*
@ -1143,7 +1128,7 @@ void FSManipRotateJoint::highlightManipulators(S32 x, S32 y)
mHighlightedPart = LL_NO_PART;
// Instead of using mObjectSelection->getFirstMoveableObject(),
// simply require that the joint (and the avatar) is valid.
if (!mJoint || !mAvatar || mAvatar->isDead())
if (!isAvatarJointSafeToUse())
{
highlightHoverSpheres(x, y);
gViewerWindow->setCursor(UI_CURSOR_ARROW);
@ -1153,25 +1138,24 @@ void FSManipRotateJoint::highlightManipulators(S32 x, S32 y)
// Decide which rotation to use based on a user toggle.
LLCachedControl<bool> use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true);
// Compute the rotation center in agent space.
LLVector3 agent_space_rotation_center = gAgent.getPosAgentFromGlobal(mRotationCenter);
LLVector3 agent_space_rotation_center = mAvatar->getPosAgentFromGlobal(mRotationCenter);
// Update joint world matrices.
mJoint->updateWorldMatrixParent();
mJoint->updateWorldMatrix();
const LLQuaternion joint_world_rotation = mJoint->getWorldRotation();
const LLQuaternion joint_world_rotation = getSelectedJointWorldRotation();
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.
@ -1308,14 +1292,18 @@ bool FSManipRotateJoint::handleHover(S32 x, S32 y, MASK mask)
{
highlightManipulators(x, y);
}
return true;
}
LLQuaternion FSManipRotateJoint::dragUnconstrained(S32 x, S32 y)
{
if (!isAvatarJointSafeToUse())
return LLQuaternion();
// Get the camera position and the joints pivot (in agent space)
LLVector3 cam = gAgentCamera.getCameraPositionAgent();
LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter);
LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter);
// Compute the current intersection on the sphere.
mMouseCur = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters);
@ -1388,27 +1376,29 @@ static LLQuaternion extractTwist(const LLQuaternion& rot, const LLVector3& axis)
LLVector3 proj = axis * dot; // proj is now purely along 'axis'
// Build the “twist” quaternion from (proj, w), then renormalize
LLQuaternion twist(proj.mV[VX],
proj.mV[VY],
proj.mV[VZ],
w);
LLQuaternion twist(proj.mV[VX], proj.mV[VY], proj.mV[VZ], w);
if (w < 0.f)
{
{
twist = -twist;
}
twist.normalize();
return twist;
}
LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y)
{
if (!isAvatarJointSafeToUse())
return LLQuaternion();
// Get the constraint axis from our joint manipulator.
LLVector3 constraint_axis = getConstraintAxis();
LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter);
LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter);
if (mCamEdgeOn)
{
LLQuaternion freeRot = dragUnconstrained(x, y);
return extractTwist(freeRot, constraint_axis);
}
// 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);
@ -1416,6 +1406,7 @@ LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y)
{
return LLQuaternion::DEFAULT;
}
projected_mouse -= agent_space_center;
projected_mouse.normalize();
@ -1424,6 +1415,7 @@ LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y)
initial_proj -= (initial_proj * constraint_axis) * constraint_axis;
initial_proj.normalize();
//float angle = acos(initial_proj * projected_mouse); // angle in (-pi, pi)
// 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;
@ -1431,14 +1423,16 @@ LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y)
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;
if (!updateVisiblity())
return;
LLQuaternion delta_rot;
LLQuaternion delta_send, delta_rot;
if (mManipPart == LL_ROT_GENERAL)
{
delta_rot = dragUnconstrained(x, y);
@ -1447,10 +1441,31 @@ void FSManipRotateJoint::drag(S32 x, S32 y)
{
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);
delta_send.set(delta_rot);
delta_send *= ~mLastSetRotation;
mLastSetRotation.set(delta_rot);
delta_send = mSavedJointRot * delta_send * ~mSavedJointRot;
switch (mReferenceFrame)
{
case POSER_FRAME_CAMERA:
case POSER_FRAME_AVATAR:
delta_send.conjugate();
break;
case POSER_FRAME_WORLD:
delta_send.mQ[VX] *= -1;
break;
case POSER_FRAME_BONE:
default:
break;
}
auto* poser = dynamic_cast<FSFloaterPoser*>(LLFloaterReg::findInstance("fs_poser"));
if (poser && mJoint)
poser->updatePosedBones(mJoint->getName(), delta_send, LLVector3::zero, LLVector3::zero);
}
// set mConstrainedAxis based on mManipParat and returns it too.
@ -1478,10 +1493,10 @@ LLVector3 FSManipRotateJoint::setConstraintAxis()
// Transform the local axis into world space using the joint's world rotation.
if (mJoint)
{
LLCachedControl<bool> use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true);
LLCachedControl<bool> 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;
@ -1493,12 +1508,41 @@ LLVector3 FSManipRotateJoint::setConstraintAxis()
}
else
{
active_rotation = mJoint->getWorldRotation();
active_rotation = getSelectedJointWorldRotation();
}
axis = axis * active_rotation;
axis.normalize();
}
}
mConstraintAxis = axis;
return axis;
}
LLQuaternion FSManipRotateJoint::getSelectedJointWorldRotation()
{
LLQuaternion joinRot;
if (!mJoint || !mAvatar)
return joinRot;
auto* poser = dynamic_cast<FSFloaterPoser*>(LLFloaterReg::findInstance("fs_poser"));
if (!poser)
return joinRot;
return poser->getManipGimbalRotation(mJoint->getName());
}
bool FSManipRotateJoint::isAvatarJointSafeToUse()
{
if (!mJoint || !mAvatar)
return false;
if (mAvatar->isDead() || !mAvatar->isFullyLoaded())
{
setAvatar(nullptr);
return false;
}
return true;
}

View File

@ -1,5 +1,5 @@
/**
* @file fsmaniproatejoint.h
* @file fsmaniprotatejoint.h
* @brief custom manipulator for rotating joints
*
* $LicenseInfo:firstyear=2024&license=viewerlgpl$
@ -32,11 +32,22 @@
#include "llmaniprotate.h"
class LLJoint;
class LLVOAvatar; // or LLVOAvatarSelf, etc.
class LLVOAvatar; // for LLVOAvatarSelf, etc.
/// <summary>
/// A set of reference frames for presenting the gimbal within.
/// </summary>
typedef enum E_PoserReferenceFrame
{
POSER_FRAME_BONE = 0, // frame is the bone the gimbal is centered on
POSER_FRAME_WORLD = 1, // frame is world North-South-East-West-Up-Down
POSER_FRAME_AVATAR = 2, // frame is mPelvis rotation
POSER_FRAME_CAMERA = 3, // frame is defined by vector of camera position to bone position
} E_PoserReferenceFrame;
namespace {
const F32 AXIS_ONTO_CAM_TOLERANCE = cos( 85.f * DEG_TO_RAD ); // cos() is not constexpr til c++26
constexpr F32 RADIUS_PIXELS = 100.f; // size in screen space
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;
@ -74,11 +85,25 @@ public:
FSManipRotateJoint(LLToolComposite* composite);
virtual ~FSManipRotateJoint() {}
static std::string getManipPartString(EManipPart part);
// Called to designate which joint we are going to manipulate.
/// <summary>
/// Sets the joint we are going to manipulate.
/// </summary>
/// <param name="joint">The joint to interact with.</param>
void setJoint(LLJoint* joint);
/// <summary>
/// Sets the avatar the manip should interact with.
/// </summary>
/// <param name="avatar">The avatar to interact with.</param>
void setAvatar(LLVOAvatar* avatar);
/// <summary>
/// Sets the reference frame for the manipulator.
/// </summary>
/// <param name="avatar">The E_PoserReferenceFrame to use.</param>
void setReferenceFrame(const E_PoserReferenceFrame frame) { mReferenceFrame = frame; };
// Overrides
void handleSelect() override;
bool updateVisiblity() override;
@ -138,8 +163,13 @@ private:
void renderRingPass(const RingRenderParams& params, float radius, float width, int pass);
void renderAxes(const LLVector3& center, F32 size, const LLQuaternion& rotation);
bool isAvatarJointSafeToUse();
LLQuaternion getSelectedJointWorldRotation();
float mLastAngle = 0.f;
LLVector3 mConstraintAxis;
E_PoserReferenceFrame mReferenceFrame = POSER_FRAME_BONE;
LLQuaternion mLastSetRotation;
};
#endif // FS_MANIP_ROTATE_JOINT_H

View File

@ -29,6 +29,7 @@
#include "fsposeranimator.h"
#include "llcharacter.h"
#include "llagent.h"
#include "llagentcamera.h"
#include "fsposingmotion.h"
std::map<LLUUID, LLAssetID> FSPoserAnimator::sAvatarIdToRegisteredAnimationId;
@ -52,6 +53,25 @@ bool FSPoserAnimator::isPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint
return posingMotion->currentlyPosingJoint(jointPose);
}
bool FSPoserAnimator::hasJointBeenChanged(LLVOAvatar* avatar, const FSPoserJoint& joint)
{
if (!isAvatarSafeToUse(avatar))
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
if (posingMotion->isStopped())
return false;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return false;
return jointPose->getJointModified();
}
void FSPoserAnimator::setPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, bool shouldPose)
{
if (!isAvatarSafeToUse(avatar))
@ -212,7 +232,8 @@ LLVector3 FSPoserAnimator::getJointPosition(LLVOAvatar* avatar, const FSPoserJoi
return jointPose->getPublicPosition();
}
void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_BoneDeflectionStyles style)
void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_PoserReferenceFrame frame,
E_BoneDeflectionStyles style)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -231,7 +252,8 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j
if (!jointPose)
return;
LLVector3 positionDelta = jointPose->getPublicPosition() - position;
LLVector3 jointPosition = jointPose->getPublicPosition();
LLVector3 positionDelta = jointPosition - position;
switch (style)
{
@ -239,13 +261,13 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j
case MIRROR_DELTA:
case SYMPATHETIC_DELTA:
case SYMPATHETIC:
jointPose->setPublicPosition(position);
jointPose->setPublicPosition(jointPosition - positionDelta);
break;
case DELTAMODE:
case NONE:
default:
jointPose->setPublicPosition(position);
jointPose->setPublicPosition(jointPosition - positionDelta);
return;
}
@ -406,6 +428,7 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar)
return;
posingMotion->setAllRotationsToZeroAndClearUndo();
mPosingState.purgeMotionStates(avatar);
for (size_t index = 0; index != PoserJoints.size(); ++index)
{
@ -420,10 +443,9 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar)
posingMotion->setJointBvhLock(jointPose, false);
}
}
void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation)
void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -440,8 +462,9 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi
setPosingAvatarJoint(avatar, joint, true);
}
void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style)
void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion rotation,
const LLVector3 position, const LLVector3 scale)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -454,27 +477,34 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
if (!jointPose)
return;
LLQuaternion deltaRot = jointPose->recaptureJointAsDelta(resetBaseRotationToZero);
LLQuaternion framedRotation = changeToRotationFrame(avatar, rotation, frame, jointPose);
jointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * jointPose->getPublicRotation());
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
deRotateWorldLockedDescendants(joint, posingMotion, framedRotation);
if (style == NONE || style == DELTAMODE)
return;
auto oppositePoserJoint = getPoserJointByName(joint->mirrorJointName());
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName());
if (!oppositeJointPose)
return;
LLQuaternion mirroredRotation = LLQuaternion(-framedRotation.mQ[VX], framedRotation.mQ[VY], -framedRotation.mQ[VZ], framedRotation.mQ[VW]);
switch (style)
{
case SYMPATHETIC:
case SYMPATHETIC_DELTA:
oppositeJointPose->cloneRotationFrom(jointPose);
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, framedRotation);
break;
case MIRROR:
case MIRROR_DELTA:
oppositeJointPose->mirrorRotationFrom(jointPose);
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation);
break;
default:
@ -482,6 +512,54 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
}
}
LLQuaternion FSPoserAnimator::getManipGimbalRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, E_PoserReferenceFrame frame)
{
LLQuaternion globalRot(-1.f, 0.f, 0.f, 0.f);
if (frame == POSER_FRAME_WORLD)
return globalRot;
if (!joint)
return globalRot;
if (!isAvatarSafeToUse(avatar))
return globalRot;
if (frame == POSER_FRAME_AVATAR)
{
LLJoint* pelvis = avatar->getJoint("mPelvis");
if (pelvis)
return pelvis->getWorldRotation();
return globalRot;
}
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return globalRot;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName());
if (!jointPose)
return globalRot;
LLJoint* llJoint = jointPose->getJointState()->getJoint();
if (!llJoint)
return globalRot;
if (frame == POSER_FRAME_BONE)
return llJoint->getWorldRotation();
LLVector3 skyward(0.f, 0.f, 1.f);
LLVector3 left(1.f, 0.f, 0.f);
LLVector3 up, jointToCameraPosition, jointPosition;
jointPosition = llJoint->getWorldPosition();
jointToCameraPosition = jointPosition - gAgentCamera.getCameraPositionAgent();
jointToCameraPosition.normalize();
left.setVec(skyward % jointToCameraPosition);
up.setVec(jointToCameraPosition % left);
return LLQuaternion(jointToCameraPosition, left, up);
}
LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const
{
auto rotation = getJointRotation(avatar, joint, SWAP_NOTHING, NEGATE_NOTHING);
@ -525,12 +603,13 @@ LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoi
if (!jointPose)
return vec3;
return translateRotationFromQuaternion(translation, negation, jointPose->getPublicRotation());
return translateRotationFromQuaternion(jointPose, translation, negation, jointPose->getPublicRotation());
}
void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation,
const LLVector3& deltaRotation, E_BoneDeflectionStyles deflectionStyle,
E_BoneAxisTranslation translation, S32 negation, bool resetBaseRotationToZero, E_RotationStyle rotationStyle)
const LLVector3& deltaRotation, E_BoneDeflectionStyles style, E_PoserReferenceFrame frame,
E_BoneAxisTranslation translation, S32 negation, bool resetBaseRotationToZero,
E_RotationStyle rotationStyle)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -545,13 +624,15 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
if (!jointPose)
return;
LLQuaternion absRot = translateRotationToQuaternion(translation, negation, absRotation);
LLQuaternion deltaRot = translateRotationToQuaternion(translation, negation, deltaRotation);
switch (deflectionStyle)
bool translationRequiresDelta = frame != POSER_FRAME_BONE;
LLQuaternion absRot = translateRotationToQuaternion(avatar, jointPose, frame, translation, negation, absRotation);
LLQuaternion deltaRot = translateRotationToQuaternion(avatar, jointPose, frame, translation, negation, deltaRotation);
switch (style)
{
case SYMPATHETIC:
case MIRROR:
if (rotationStyle == DELTAIC_ROT)
if (rotationStyle == DELTAIC_ROT || translationRequiresDelta)
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
else
jointPose->setPublicRotation(resetBaseRotationToZero, absRot);
@ -570,7 +651,7 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
case NONE:
default:
if (rotationStyle == DELTAIC_ROT)
if (rotationStyle == DELTAIC_ROT || translationRequiresDelta)
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
else
jointPose->setPublicRotation(resetBaseRotationToZero, absRot);
@ -586,8 +667,8 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
if (!oppositeJointPose)
return;
LLQuaternion inv_quat = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]);
switch (deflectionStyle)
LLQuaternion mirroredRotation = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]);
switch (style)
{
case SYMPATHETIC:
oppositeJointPose->cloneRotationFrom(jointPose);
@ -604,13 +685,13 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
case MIRROR:
oppositeJointPose->mirrorRotationFrom(jointPose);
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, inv_quat);
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation);
break;
case MIRROR_DELTA:
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, inv_quat * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, inv_quat);
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation);
break;
default:
@ -709,8 +790,9 @@ void FSPoserAnimator::flipEntirePose(LLVOAvatar* avatar)
}
}
// from the UI to the bone, the inverse translation, the un-swap, the backwards
LLQuaternion FSPoserAnimator::translateRotationToQuaternion(E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation)
// from the UI to the bone. Bone rotations we store are relative to the skeleton (or to T-Pose, if you want to visualize).
LLQuaternion FSPoserAnimator::translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame,
E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation)
{
if (negation & NEGATE_ALL)
{
@ -728,7 +810,7 @@ LLQuaternion FSPoserAnimator::translateRotationToQuaternion(E_BoneAxisTranslatio
rotation.mV[VZ] *= -1;
}
LLMatrix3 rot_mat;
LLMatrix3 rot_mat;
switch (translation)
{
case SWAP_YAW_AND_ROLL:
@ -761,11 +843,63 @@ LLQuaternion FSPoserAnimator::translateRotationToQuaternion(E_BoneAxisTranslatio
rot_quat = LLQuaternion(rot_mat) * rot_quat;
rot_quat.normalize();
rot_quat = changeToRotationFrame(avatar, rot_quat, frame, joint);
return rot_quat;
}
LLQuaternion FSPoserAnimator::changeToRotationFrame(LLVOAvatar* avatar, LLQuaternion rotation, E_PoserReferenceFrame frame, FSJointPose* joint)
{
if (!joint || !avatar)
return rotation;
LLJoint* pelvis = avatar->getJoint("mPelvis");
if (!pelvis)
return rotation;
LLVector3 skyward(0.f, 0.f, 1.f);
LLVector3 left(1.f, 0.f, 0.f);
LLVector3 forwards(0.f, 1.f, 0.f);
LLVector3 up, jointToCameraPosition, jointPosition;
LLQuaternion worldRotOfWorld(forwards, left, skyward);
LLQuaternion differenceInWorldRot, rotDiffInChildFrame, worldRotOfPelvis, worldRotOfCamera;
LLQuaternion worldRotOfThisJoint = joint->getJointState()->getJoint()->getWorldRotation();
switch (frame)
{
case POSER_FRAME_WORLD:
differenceInWorldRot = worldRotOfThisJoint * ~worldRotOfWorld;
break;
case POSER_FRAME_AVATAR:
worldRotOfPelvis = pelvis->getWorldRotation();
differenceInWorldRot = worldRotOfThisJoint * ~worldRotOfPelvis;
break;
case POSER_FRAME_CAMERA:
jointPosition = joint->getJointState()->getJoint()->getWorldPosition();
jointToCameraPosition = jointPosition - gAgentCamera.getCameraPositionAgent();
jointToCameraPosition.normalize();
left.setVec(skyward % jointToCameraPosition);
up.setVec(jointToCameraPosition % left);
worldRotOfCamera = LLQuaternion(jointToCameraPosition, left, up);
differenceInWorldRot = worldRotOfThisJoint * ~worldRotOfCamera;
break;
case POSER_FRAME_BONE:
default:
return rotation;
}
rotDiffInChildFrame = differenceInWorldRot * rotation * ~differenceInWorldRot;
rotDiffInChildFrame.conjugate();
return rotDiffInChildFrame;
}
// from the bone to the UI; this is the 'forwards' use of the enum
LLVector3 FSPoserAnimator::translateRotationFromQuaternion(E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const
LLVector3 FSPoserAnimator::translateRotationFromQuaternion(FSJointPose* joint, E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const
{
LLVector3 vec3;
@ -833,7 +967,8 @@ LLVector3 FSPoserAnimator::getJointScale(LLVOAvatar* avatar, const FSPoserJoint&
return jointPose->getPublicScale();
}
void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_BoneDeflectionStyles style)
void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_PoserReferenceFrame frame,
E_BoneDeflectionStyles style)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -852,25 +987,46 @@ void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* join
if (!jointPose)
return;
jointPose->setPublicScale(scale);
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName());
if (!oppositeJointPose)
return;
LLVector3 jointScale = jointPose->getPublicScale();
LLVector3 scaleDelta = jointScale - scale;
switch (style)
{
case SYMPATHETIC:
case MIRROR:
case SYMPATHETIC_DELTA:
case MIRROR_DELTA:
oppositeJointPose->setPublicScale(scale);
case SYMPATHETIC_DELTA:
case SYMPATHETIC:
jointPose->setPublicScale(jointScale - scaleDelta);
break;
case DELTAMODE:
case NONE:
default:
jointPose->setPublicScale(jointScale - scaleDelta);
return;
}
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName());
if (!oppositeJointPose)
return;
LLVector3 oppositeJointScale = oppositeJointPose->getPublicScale();
switch (style)
{
case MIRROR:
case MIRROR_DELTA:
oppositeJointPose->setPublicScale(oppositeJointScale + scaleDelta);
break;
case SYMPATHETIC_DELTA:
case SYMPATHETIC:
oppositeJointPose->setPublicScale(oppositeJointScale - scaleDelta);
break;
default:
break;
}
}
bool FSPoserAnimator::tryGetJointSaveVectors(LLVOAvatar* avatar, const FSPoserJoint& joint, LLVector3* rot, LLVector3* pos,
@ -914,7 +1070,7 @@ void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint*
jointPose->purgeUndoQueue();
LLQuaternion rot = translateRotationToQuaternion(SWAP_NOTHING, NEGATE_NOTHING, rotation);
LLQuaternion rot = translateRotationToQuaternion(avatar, jointPose, POSER_FRAME_BONE, SWAP_NOTHING, NEGATE_NOTHING, rotation);
jointPose->setPublicRotation(setBaseToZero, rot);
}
@ -963,13 +1119,21 @@ void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joi
}
}
bool FSPoserAnimator::loadPosingState(LLVOAvatar* avatar, LLSD pose)
bool FSPoserAnimator::loadPosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose)
{
if (!isAvatarSafeToUse(avatar))
return false;
mPosingState.purgeMotionStates(avatar);
mPosingState.restoreMotionStates(avatar, pose);
mPosingState.restoreMotionStates(avatar, ignoreOwnership, pose);
return applyStatesToPosingMotion(avatar);
}
bool FSPoserAnimator::applyStatesToPosingMotion(LLVOAvatar* avatar)
{
if (!isAvatarSafeToUse(avatar))
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
@ -1007,9 +1171,9 @@ void FSPoserAnimator::applyJointMirrorToBaseRotations(FSPosingMotion* posingMoti
}
}
void FSPoserAnimator::savePosingState(LLVOAvatar* avatar, LLSD* saveRecord)
void FSPoserAnimator::savePosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord)
{
mPosingState.writeMotionStates(avatar, saveRecord);
mPosingState.writeMotionStates(avatar, ignoreOwnership, saveRecord);
}
const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) const
@ -1023,6 +1187,39 @@ const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const
return nullptr;
}
const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByNumber(LLVOAvatar* avatar, const int jointNumber) const
{
if (!avatar)
return nullptr;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return nullptr;
FSJointPose* parentJoint = posingMotion->getJointPoseByJointNumber(jointNumber);
if (!parentJoint)
return nullptr;
return getPoserJointByName(parentJoint->jointName());
}
bool FSPoserAnimator::tryGetJointNumber(LLVOAvatar* avatar, const FSPoserJoint &poserJoint, int &jointNumber)
{
if (!avatar)
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
FSJointPose* parentJoint = posingMotion->getJointPoseByJointName(poserJoint.jointName());
if (!parentJoint)
return false;
jointNumber = parentJoint->getJointNumber();
return jointNumber >= 0;
}
bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar)
{
if (!isAvatarSafeToUse(avatar))
@ -1038,7 +1235,6 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar)
gAgent.stopFidget();
mPosingState.captureMotionStates(avatar);
avatar->startDefaultMotions();
avatar->startMotion(posingMotion->motionId());
@ -1057,11 +1253,17 @@ void FSPoserAnimator::updatePosingState(LLVOAvatar* avatar, std::vector<FSPoserA
if (!posingMotion)
return;
std::string jointNamesRecaptured;
std::vector<int> jointNumbersRecaptured;
for (auto item : jointsRecaptured)
jointNamesRecaptured += item->jointName();
{
auto poserJoint = posingMotion->getJointPoseByJointName(item->jointName());
if (!poserJoint)
continue;
mPosingState.updateMotionStates(avatar, posingMotion, jointNamesRecaptured);
jointNumbersRecaptured.push_back(poserJoint->getJointNumber());
}
mPosingState.updateMotionStates(avatar, posingMotion, jointNumbersRecaptured);
}
void FSPoserAnimator::stopPosingAvatar(LLVOAvatar *avatar)
@ -1219,6 +1421,9 @@ void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint
void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo)
{
if (!posingMotion)
return;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return;

View File

@ -30,6 +30,7 @@
#include "fsposingmotion.h"
#include "fsposestate.h"
#include "llvoavatar.h"
#include "fsmaniprotatejoint.h"
/// <summary>
/// Describes how we will cluster the joints/bones/thingos.
@ -395,6 +396,20 @@ public:
/// <returns>The matching joint if found, otherwise nullptr</returns>
const FSPoserJoint* getPoserJointByName(const std::string& jointName) const;
/// <summary>
/// Get a PoserJoint case-insensitive-matching the supplied name.
/// </summary>
/// <param name="jointNumber">The name of the joint to match.</param>
/// <returns>The matching joint if found, otherwise nullptr</returns>
const FSPoserJoint* getPoserJointByNumber(LLVOAvatar* avatar, const int jointNumber) const;
/// <summary>
/// Get a PoserJoint by its LLJoint number.
/// </summary>
/// <param name="jointNumber">The name of the joint to match.</param>
/// <returns>The matching joint if found, otherwise nullptr</returns>
bool tryGetJointNumber(LLVOAvatar* avatar, const FSPoserJoint &poserJoint, int &jointNumber);
/// <summary>
/// Tries to start posing the supplied avatar.
/// </summary>
@ -423,6 +438,14 @@ public:
/// <returns>True if this is joint is being posed for the supplied avatar, otherwise false.</returns>
bool isPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint);
/// <summary>
/// Determines whether the supplied PoserJoint for the supplied avatar has been modified this session, even if all change has been reverted.
/// </summary>
/// <param name="avatar">The avatar having the joint to which we refer.</param>
/// <param name="joint">The joint being queried for.</param>
/// <returns>True if this is joint has been changed while posing even if the change has been reverted or undone, otherwise false.</returns>
bool hasJointBeenChanged(LLVOAvatar* avatar, const FSPoserJoint& joint);
/// <summary>
/// Sets whether the supplied PoserJoint for the supplied avatar should be posed.
/// </summary>
@ -479,8 +502,10 @@ public:
/// <param name="avatar">The avatar whose joint is to be set.</param>
/// <param name="joint">The joint to set.</param>
/// <param name="position">The position to set the joint to.</param>
/// <param name="frame">The frame to translate the position to.</param>
/// <param name="style">Any ancilliary action to be taken with the change to be made.</param>
void setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_BoneDeflectionStyles style);
void setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_PoserReferenceFrame frame,
E_BoneDeflectionStyles style);
/// <summary>
/// Gets the rotation of a joint for the supplied avatar.
@ -506,6 +531,15 @@ public:
/// </remarks>
LLVector3 getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const;
/// <summary>
/// Gets the rotation suitable for the Manip gimbal for the supplied avatar and joint.
/// </summary>
/// <param name="avatar">The avatar having the Manip gimbal placed upon it.</param>
/// <param name="joint">The joint on the avatar where the manip should be placed.</param>
/// <param name="frame">The frame of reference for the gimbal.</param>
/// <returns>The rotation to set the gimbal to.</returns>
LLQuaternion getManipGimbalRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, E_PoserReferenceFrame frame);
/// <summary>
/// Sets the rotation of a joint for the supplied avatar.
/// </summary>
@ -518,8 +552,9 @@ public:
/// <param name="negation">The style of negation to apply to the set.</param>
/// <param name="resetBaseRotationToZero">Whether to set the base rotation to zero on setting the rotation.</param>
/// <param name="rotationStyle">Whether to apply the supplied rotation as a delta to the supplied joint.</param>
void setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation, const LLVector3& deltaRotation, E_BoneDeflectionStyles style,
E_BoneAxisTranslation translation, S32 negation, bool resetBaseRotationToZero, E_RotationStyle rotationStyle);
void setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation, const LLVector3& deltaRotation,
E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, E_BoneAxisTranslation translation, S32 negation,
bool resetBaseRotationToZero, E_RotationStyle rotationStyle);
/// <summary>
/// Gets the scale of a joint for the supplied avatar.
@ -535,8 +570,10 @@ public:
/// <param name="avatar">The avatar whose joint is to be set.</param>
/// <param name="joint">The joint to set.</param>
/// <param name="scale">The scale to set the joint to.</param>
/// <param name="frame">The frame to translate the position to.</param>
/// <param name="style">Any ancilliary action to be taken with the change to be made.</param>
void setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_BoneDeflectionStyles style);
void setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_PoserReferenceFrame frame,
E_BoneDeflectionStyles style);
/// <summary>
/// Reflects the joint with its opposite if it has one, or just mirror the rotation of itself.
@ -564,9 +601,7 @@ public:
/// </summary>
/// <param name="avatar">The avatar whose joint is to be recaptured.</param>
/// <param name="joint">The joint to recapture.</param>
/// <param name="translation">The axial translation form the supplied joint.</param>
/// <param name="negation">The style of negation to apply to the recapture.</param>
void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation);
void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint);
/// <summary>
/// Recaptures any change in joint state.
@ -575,7 +610,11 @@ public:
/// <param name="joint">The joint to recapture.</param>
/// <param name="resetBaseRotationToZero">Whether to set the base rotation to zero on setting the rotation.</param>
/// <param name="style">Any ancilliary action to be taken with the change to be made.</param>
void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, E_BoneDeflectionStyles style);
/// <param name="rotation">The rotation of the supplied joint.</param>
/// <param name="position">The position of the supplied joint.</param>
/// <param name="scale">The scale of the supplied joint.</param>
void updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, E_BoneDeflectionStyles style,
E_PoserReferenceFrame frame, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale);
/// <summary>
/// Sets all of the joint rotations of the supplied avatar to zero.
@ -711,6 +750,7 @@ public:
/// Loads the posing state (base rotations) to the supplied avatars posing-motion, from the supplied record.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be loaded.</param>
/// <param name="ignoreOwnership">Whether to ignore ownership. For use when reading a local file.</param>
/// <param name="pose">The record to read the posing state from.</param>
/// <returns>True if the pose loaded successfully, otherwise false.</returns>
/// <remarks>
@ -718,14 +758,22 @@ public:
/// it can take several frames for the animation to be loaded and ready.
/// It may therefore be necessary to attempt this several times.
/// </remarks>
bool loadPosingState(LLVOAvatar* avatar, LLSD pose);
bool loadPosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose);
/// <summary>
/// Applies the posing states to the posing motion for the supplied avatar.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be loaded.</param>
/// <returns>True if the state applied successfully, otherwise false.</returns>
bool applyStatesToPosingMotion(LLVOAvatar* avatar);
/// <summary>
/// Adds the posing state for the supplied avatar to the supplied record.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be written.</param>
/// <param name="ignoreOwnership">Whether to ignore ownership while saving.</param>
/// <param name="saveRecord">The record to write the posing state to.</param>
void savePosingState(LLVOAvatar* avatar, LLSD* saveRecord);
void savePosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord);
/// <summary>
/// Purges and recaptures the pose state for the supplied avatar.
@ -734,17 +782,6 @@ public:
/// <param name="jointsRecaptured">The joints which were recaptured.</param>
void updatePosingState(LLVOAvatar* avatar, std::vector<FSPoserAnimator::FSPoserJoint*> jointsRecaptured);
/// <summary>
/// Add a new posing state, or updates the matching posing state with the supplied data.
/// </summary>
/// <param name="avatar">The avatar the posing state is intended for.</param>
/// <param name="animId">The ID of the animation.</param>
/// <param name="updateTime">The frame-time of the animation.</param>
/// <param name="jointNames">The names of the joints, if any, the animation should specifically be applied to.</param>
/// <param name="captureOrder">The capture order.</param>
/// <returns>True if the posing state was added or changed by the update data, otherwise false.</returns>
bool addOrUpdatePosingState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder);
/// <summary>
/// Traverses the joints and applies reversals to the base rotations if needed.
/// </summary>
@ -755,14 +792,19 @@ public:
void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion);
private:
/// <summary>
/// Translates a rotation vector from the UI to a Quaternion for the bone.
/// This also performs the axis-swapping the UI needs for up/down/left/right to make sense.
/// </summary>
/// <param name="translation">The axis translation to perform.</param>
/// <param name="rotation">The rotation to transform to quaternion.</param>
/// <returns>The rotation quaternion.</returns>
LLQuaternion translateRotationToQuaternion(E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation);
/// <summary>
/// Translates the supplied rotation vector from UI to a Quaternion for the bone.
/// Also performs the axis-swapping and other transformations for up/down/left/right to make sense.
/// </summary>
/// <param name="avatar">The avatar whose joint is being manipulated.</param>
/// <param name="joint">The joint which is being altered.</param>
/// <param name="frame">The frame of reference the translation should be performed in.</param>
/// <param name="translation">The axis translation to perform.</param>
/// <param name="negation">The style of axis-negation.</param>
/// <param name="rotation">The rotation to translate and transform to quaternion.</param>
/// <returns>The translated rotation quaternion.</returns>
LLQuaternion translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame,
E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation);
/// <summary>
/// Translates a bone-rotation quaternion to a vector usable easily on the UI.
@ -770,7 +812,7 @@ public:
/// <param name="translation">The axis translation to perform.</param>
/// <param name="rotation">The rotation to transform to matrix.</param>
/// <returns>The rotation vector.</returns>
LLVector3 translateRotationFromQuaternion(E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const;
LLVector3 translateRotationFromQuaternion(FSJointPose* joint, E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const;
/// <summary>
/// Creates a posing motion for the supplied avatar.
@ -844,6 +886,20 @@ public:
/// <param name="redo">Whether to redo the edit, otherwise the edit is undone.</param>
void undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo);
/// <summary>
/// Converts the supplied rotation into the desired frame.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="rotation">The rotation to convert.</param>
/// <param name="frame">The frame to translate the rotation to.</param>
/// <param name="joint">The joint whose rotation is being changed.</param>
/// <remarks>
/// Input rotations have no implicit frame: it's just a rotation and ordinarily applied, inherits the joint's rotational framing.
/// This method imposes a framing upon the supplied rotation, meaning user input is considered as relative to something like
/// 'the world', 'avatar pelvis' or the position of the camera relative to the joint.
/// </remarks>
LLQuaternion changeToRotationFrame(LLVOAvatar* avatar, LLQuaternion rotation, E_PoserReferenceFrame frame, FSJointPose* joint);
/// <summary>
/// Maps the avatar's ID to the animation registered to them.
/// Thus we start/stop the same animation, and get/set the same rotations etc.

View File

@ -3,6 +3,7 @@
std::map<LLUUID, std::vector<FSPoseState::fsMotionState>> FSPoseState::sMotionStates;
std::map<LLUUID, int> FSPoseState::sCaptureOrder;
std::map<LLUUID, bool> FSPoseState::sMotionStatesOwnedByMe;
void FSPoseState::captureMotionStates(LLVOAvatar* avatar)
{
@ -10,6 +11,7 @@ void FSPoseState::captureMotionStates(LLVOAvatar* avatar)
return;
sCaptureOrder[avatar->getID()] = 0;
int animNumber = 0;
for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); ++anim_it)
{
@ -21,27 +23,26 @@ void FSPoseState::captureMotionStates(LLVOAvatar* avatar)
newState.motionId = anim_it->first;
newState.lastUpdateTime = motion->getLastUpdateTime();
newState.captureOrder = 0;
newState.avatarOwnsPose = canSaveMotionId(avatar, anim_it->first);
newState.inLayerOrder = animNumber++;
newState.gAgentOwnsPose = canSaveMotionId(avatar, anim_it->first);
sMotionStates[avatar->getID()].push_back(newState);
}
}
void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured)
void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::vector<S32> jointNumbersRecaptured)
{
if (!avatar || !posingMotion)
return;
sCaptureOrder[avatar->getID()]++;
int animNumber = 0;
// if an animation for avatar is a subset of jointNamesRecaptured, delete it
// if an animation for avatar is a subset of jointNumbersRecaptured, delete it
// this happens on second/subsequent recaptures; the first recapture is no longer needed
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end();)
{
std::string joints = (*it).jointNamesAnimated;
bool recaptureMatches = !joints.empty() && !jointNamesRecaptured.empty() && jointNamesRecaptured.find(joints) != std::string::npos;
if (recaptureMatches)
if (vector2IsSubsetOfVector1(jointNumbersRecaptured, (*it).jointNumbersAnimated))
it = sMotionStates[avatar->getID()].erase(it);
else
it++;
@ -53,7 +54,7 @@ void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingM
if (!motion)
continue;
if (!posingMotion->otherMotionAnimatesJoints(motion, jointNamesRecaptured))
if (!posingMotion->otherMotionAnimatesJoints(motion, jointNumbersRecaptured))
continue;
bool foundMatch = false;
@ -71,45 +72,17 @@ void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingM
continue;
fsMotionState newState;
newState.motionId = anim_it->first;
newState.lastUpdateTime = motion->getLastUpdateTime();
newState.jointNamesAnimated = jointNamesRecaptured;
newState.captureOrder = sCaptureOrder[avatar->getID()];
newState.avatarOwnsPose = canSaveMotionId(avatar, anim_it->first);
newState.motionId = anim_it->first;
newState.lastUpdateTime = motion->getLastUpdateTime();
newState.jointNumbersAnimated = jointNumbersRecaptured;
newState.captureOrder = sCaptureOrder[avatar->getID()];
newState.inLayerOrder = animNumber++;
newState.gAgentOwnsPose = canSaveMotionId(avatar, anim_it->first);
sMotionStates[avatar->getID()].push_back(newState);
}
}
bool FSPoseState::addOrUpdatePosingMotionState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder)
{
if (!avatar)
return false;
bool foundMatch = false;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
bool motionIdMatches = (*it).motionId == animId;
bool updateTimesMatch = (*it).lastUpdateTime == updateTime;
bool jointNamesMatch = (*it).jointNamesAnimated == jointNames;
bool captureOrdersMatch = (*it).captureOrder == captureOrder;
foundMatch = motionIdMatches && updateTimesMatch && jointNamesMatch && captureOrdersMatch;
if (foundMatch)
return false;
}
fsMotionState newState;
newState.motionId = animId;
newState.lastUpdateTime = updateTime;
newState.jointNamesAnimated = jointNames;
newState.captureOrder = captureOrder;
newState.avatarOwnsPose = false;
sMotionStates[avatar->getID()].push_back(newState);
return true;
}
void FSPoseState::purgeMotionStates(LLVOAvatar* avatar)
{
if (!avatar)
@ -118,7 +91,7 @@ void FSPoseState::purgeMotionStates(LLVOAvatar* avatar)
sMotionStates[avatar->getID()].clear();
}
void FSPoseState::writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord)
void FSPoseState::writeMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord)
{
if (!avatar)
return;
@ -126,18 +99,27 @@ void FSPoseState::writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord)
int animNumber = 0;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); ++it)
{
if (!it->avatarOwnsPose)
continue;
if (!ignoreOwnership && !it->gAgentOwnsPose)
{
if (it->requeriedAssetInventory)
continue;
std::string uniqueAnimId = "poseState" + std::to_string(animNumber++);
(*saveRecord)[uniqueAnimId]["animationId"] = it->motionId.asString();
(*saveRecord)[uniqueAnimId]["lastUpdateTime"] = it->lastUpdateTime;
(*saveRecord)[uniqueAnimId]["jointNamesAnimated"] = it->jointNamesAnimated;
(*saveRecord)[uniqueAnimId]["captureOrder"] = it->captureOrder;
it->gAgentOwnsPose = canSaveMotionId(avatar, it->motionId);
it->requeriedAssetInventory = true;
if (!it->gAgentOwnsPose)
continue;
}
std::string uniqueAnimId = "poseState" + std::to_string(animNumber++);
(*saveRecord)[uniqueAnimId]["animationId"] = it->motionId.asString();
(*saveRecord)[uniqueAnimId]["lastUpdateTime"] = it->lastUpdateTime;
(*saveRecord)[uniqueAnimId]["jointNumbersAnimated"] = encodeVectorToString(it->jointNumbersAnimated);
(*saveRecord)[uniqueAnimId]["captureOrder"] = it->captureOrder;
(*saveRecord)[uniqueAnimId]["inLayerOrder"] = it->inLayerOrder;
}
}
void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, LLSD pose)
void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose)
{
if (!avatar)
return;
@ -153,25 +135,33 @@ void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, LLSD pose)
continue;
fsMotionState newState;
newState.avatarOwnsPose = true;
if (control_map.has("animationId"))
{
std::string const name = control_map["animationId"].asString();
LLUUID animId;
if (LLUUID::parseUUID(name, &animId))
{
newState.motionId = animId;
newState.gAgentOwnsPose = ignoreOwnership || canSaveMotionId(avatar, animId);
if (ignoreOwnership)
sMotionStatesOwnedByMe[animId] = true;
}
}
if (control_map.has("lastUpdateTime"))
newState.lastUpdateTime = (F32)control_map["lastUpdateTime"].asReal();
if (control_map.has("jointNamesAnimated"))
newState.jointNamesAnimated = control_map["jointNamesAnimated"].asString();
if (control_map.has("jointNumbersAnimated"))
newState.jointNumbersAnimated = decodeStringToVector(control_map["jointNumbersAnimated"].asString());
if (control_map.has("captureOrder"))
newState.captureOrder = control_map["captureOrder"].asInteger();
if (control_map.has("inLayerOrder"))
newState.inLayerOrder = control_map["inLayerOrder"].asInteger();
if (newState.captureOrder > sCaptureOrder[avatar->getID()])
sCaptureOrder[avatar->getID()] = newState.captureOrder;
@ -207,7 +197,7 @@ bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMo
resetPriorityForCaptureOrder(avatar, posingMotion, lastCaptureOrder);
}
it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNamesAnimated);
it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNumbersAnimated);
}
else
{
@ -225,26 +215,46 @@ void FSPoseState::resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotio
{
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
if (it->jointNamesAnimated.empty())
if (it->jointNumbersAnimated.empty())
continue;
if (it->motionApplied)
continue;
if (it->captureOrder != captureOrder)
continue;
posingMotion->resetBonePriority(it->jointNamesAnimated);
posingMotion->resetBonePriority(it->jointNumbersAnimated);
}
}
bool FSPoseState::canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId)
bool FSPoseState::canSaveMotionId(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId)
{
if (!gAgentAvatarp || gAgentAvatarp.isNull())
return false;
if (sMotionStatesOwnedByMe[motionId])
return true;
// does the animation exist in inventory
LLInventoryItem* item = gInventory.getItem(motionId);
if (item && item->getPermissions().getOwner() == avatar->getID())
return true;
if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID())
{
sMotionStatesOwnedByMe[motionId] = true;
return sMotionStatesOwnedByMe[motionId];
}
if (!avatarPlayingMotionId)
return false;
if (avatarPlayingMotionId->getID() == gAgentAvatarp->getID())
return motionIdIsAgentAnimationSource(motionId);
return motionIdIsFromPrimAgentOwnsAgentIsSittingOn(avatarPlayingMotionId, motionId);
}
bool FSPoseState::motionIdIsAgentAnimationSource(LLAssetID motionId)
{
if (!gAgentAvatarp || gAgentAvatarp.isNull())
return false;
for (const auto& [anim_object_id, anim_anim_id] : gAgentAvatarp->mAnimationSources)
{
@ -252,17 +262,150 @@ bool FSPoseState::canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId)
continue;
// is the item that started the anim in inventory
item = gInventory.getItem(anim_object_id);
if (item && item->getPermissions().getOwner() == avatar->getID())
return true;
LLInventoryItem* item = gInventory.getItem(anim_object_id);
if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID())
{
sMotionStatesOwnedByMe[motionId] = true;
return sMotionStatesOwnedByMe[motionId];
}
// is the item that start the animation in-world
LLViewerObject* object = gObjectList.findObject(anim_object_id);
if (object && object->permYouOwner())
return true;
return false;
{
sMotionStatesOwnedByMe[motionId] = true;
return sMotionStatesOwnedByMe[motionId];
}
}
return false;
}
bool FSPoseState::motionIdIsFromPrimAgentOwnsAgentIsSittingOn(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId)
{
if (!avatarPlayingMotionId)
return false;
const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(avatarPlayingMotionId->getRoot());
if (!agentRoot)
return false;
const LLUUID& assetIdTheyAreSittingOn = agentRoot->getID();
if (assetIdTheyAreSittingOn == avatarPlayingMotionId->getID())
return false; // they are not sitting on a thing
LLViewerObject* object = gObjectList.findObject(assetIdTheyAreSittingOn);
if (!object || !object->permYouOwner())
return false; // gAgent does not own what they are sitting on
if (object->isInventoryPending())
return false;
if (object->isInventoryDirty() || !object->getInventoryRoot())
{
object->requestInventory();
return false; // whatever they are sitting on, we don't have the inventory list for yet
}
LLInventoryItem* item = object->getInventoryItemByAsset(motionId, LLAssetType::AT_ANIMATION);
if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID())
{
sMotionStatesOwnedByMe[motionId] = true;
return sMotionStatesOwnedByMe[motionId];
}
return false;
}
bool FSPoseState::vector2IsSubsetOfVector1(std::vector<S32> newRecapture, std::vector<S32> oldRecapture)
{
if (newRecapture.size() < 1)
return false;
if (oldRecapture.size() < 1)
return false;
if (newRecapture.size() < oldRecapture.size())
return false;
for (S32 number : oldRecapture)
if (std::find(newRecapture.begin(), newRecapture.end(), number) == newRecapture.end())
return false;
return true;
}
std::string FSPoseState::encodeVectorToString(std::vector<S32> vector)
{
std::string encoded = "";
if (vector.size() < 1)
return encoded;
for (S32 numberToEncode : vector)
{
if (numberToEncode > 251) // max 216 at time of writing
continue;
S32 number = numberToEncode;
if (number >= 189)
{
encoded += "~";
number -= 189;
}
if (number >= 126)
{
encoded += "}";
number -= 126;
}
if (number >= 63)
{
encoded += "|";
number -= 63;
}
encoded += char(number + int('?'));
}
return encoded;
}
std::vector<S32> FSPoseState::decodeStringToVector(std::string vector)
{
std::vector<S32> decoded;
if (vector.empty())
return decoded;
S32 number = 0;
for (char ch : vector)
{
if (ch > '~' || ch < '?')
continue;
if (ch == '~')
{
number += 189;
continue;
}
if (ch == '}')
{
number += 126;
continue;
}
if (ch == '|')
{
number += 63;
continue;
}
number -= int('?');
number += S32(ch);
decoded.push_back(number);
number = 0;
}
return decoded;
}

View File

@ -49,18 +49,7 @@ public:
/// <param name="avatar">The avatar whose animations are to be captured.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="jointNamesRecaptured">The names of the joints being recaptured.</param>
void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured);
/// <summary>
/// Add a new posing state, or updates the matching posing state with the supplied data.
/// </summary>
/// <param name="avatar">The avatar the posing state is intended for.</param>
/// <param name="animId">The ID of the animation.</param>
/// <param name="updateTime">The frame-time of the animation.</param>
/// <param name="jointNames">The names of the joints, if any, the animation should specifically be applied to.</param>
/// <param name="captureOrder">The capture order.</param>
/// <returns>True if the posing state was added or changed by the update data, otherwise false.</returns>
bool addOrUpdatePosingMotionState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder);
void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::vector<S32> jointNamesRecaptured);
/// <summary>
/// Removes all current animation states for the supplied avatar.
@ -72,16 +61,17 @@ public:
/// Writes any documented poses for the supplied avatar to the supplied stream.
/// </summary>
/// <param name="avatar">The avatar whose animations may have been captured.</param>
/// <param name="ignoreOwnership">Whether to ignore ownership. For use when preparing saveRecord to send to another by collab.</param>
/// <param name="saveRecord">The record to add to.</param>
void writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord);
void writeMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord);
/// <summary>
/// Restores pose state(s) from the supplied record.
/// </summary>
/// <param name="avatar">The avatar whose animations may have been captured.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="ignoreOwnership">Whether to ignore ownership. For use when reading a local file.</param>
/// <param name="pose">The record to read from.</param>
void restoreMotionStates(LLVOAvatar* avatar, LLSD pose);
void restoreMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose);
/// <summary>
/// Applies the motion states for the supplied avatar to the supplied motion.
@ -122,20 +112,30 @@ private:
bool motionApplied = false;
/// <summary>
/// Whether the avatar owns the pose, or the pose was loaded.
/// For non-gAgent, we permit a query of the inventory of a prim they are sitting on.
/// Because this involves latency, we may retry ownership checking at save-time.
/// </summary>
bool avatarOwnsPose = false;
bool requeriedAssetInventory = false;
/// <summary>
/// When reloading, larger numbers are loaded last, nesting order and priority.
/// This is used to represent recaptures, where joints could be animated with different poses.
/// Whether gAgent owns the pose, or the pose was loaded from XML.
/// </summary>
bool gAgentOwnsPose = false;
/// <summary>
/// Represents 'capture layers: how the user layers animations 'on top of' others.
/// </summary>
int captureOrder = 0;
/// <summary>
/// When reloading, and if not-empty, the names of the bones this motionId should affect.
/// Represents in-layer order of capture.
/// </summary>
std ::string jointNamesAnimated;
int inLayerOrder = 0;
/// <summary>
/// When reloading, and if not-empty, the bone-numbers this motionId should affect.
/// </summary>
std ::vector<S32> jointNumbersAnimated;
};
/// <summary>
@ -147,12 +147,58 @@ private:
void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder);
/// <summary>
/// Gets whether the supplied avatar owns, and thus can save information about the supplied asset ID.
/// Gets whether gAgentID owns, and thus can save information about the supplied motionId.
/// </summary>
/// <param name="avatar">The avatar to query ownership for.</param>
/// <param name="motionId">The asset ID of the object.</param>
/// <returns>True if the avatar owns the asset, otherwise false.</returns>
bool canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId);
/// <param name="avatarPlayingMotionId">The avatar playing the supplied motionId.</param>
/// <param name="motionId">The motionId of the animation.</param>
/// <returns>True if the gAgent owns the motionId, otherwise false.</returns>
/// <remarks>
/// This only works reliably for self.
/// For motions playing on others, the motion needs to be an asset in gAgent's inventory.
/// </remarks>
bool canSaveMotionId(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId);
/// <summary>
/// Examines gAgent's animation source list for the supplied animation Id.
/// </summary>
/// <param name="motionId">The ID of the motion to query.</param>
/// <returns>True if gAgent is playing the animation, otherwise false.</returns>
bool motionIdIsAgentAnimationSource(LLAssetID motionId);
/// <summary>
/// Queries a specific condition of the supplied animation ID.
/// </summary>
/// <param name="avatarPlayingMotionId">The avatar to query for.</param>
/// <param name="motionId">The motion ID to query for.</param>
/// <returns>
/// True if the supplied avatar is sitting on an object owned by gAgent, and that object
/// contains an animation asset with the same assetId.
/// </returns>
/// <remarks>
/// This is intended to test for a situation a photographer might arrange.
/// If you are sitting on photographer's prim, playing photographer's pose, and photographer wants to save their work,
/// this allows them to save the Animation ID and state to XML.
/// It is intended this be called twice at least, as it does not implement a callback onInventoryLoaded.
/// Presently this works fine: first time being when posing starts, second when pose is saved.
/// </remarks>
bool motionIdIsFromPrimAgentOwnsAgentIsSittingOn(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId);
/// <summary>
/// Tests if all the members of supplied vector2 are members of supplied vector1.
/// </summary>
/// <param name="vector1">The super-set.</param>
/// <param name="vector2">The possible sub-set.</param>
/// <returns>True if all members of vector2 are members of vector1, otherwise false.</returns>
bool vector2IsSubsetOfVector1(std::vector<S32> vector1, std::vector<S32> vector2);
/// <summary>
/// Two symmetric methods for (de)serializing vectors to both XML and collab-safe short-as-possible strings and back again.
/// </summary>
/// <remarks>
/// Collab-safe means ASCII-printable chars, and delimiter usage does not conflict with Collab's delimiter.
/// </remarks>
std::string encodeVectorToString(std::vector<S32> vector);
std::vector<S32> decodeStringToVector(std::string vector);
struct compareByCaptureOrder
{
@ -160,6 +206,8 @@ private:
{
if (a.captureOrder < b.captureOrder)
return true; // Ascending order
if (a.captureOrder == b.captureOrder && a.inLayerOrder < b.inLayerOrder)
return true; // Ascending order in layer
return false;
}
@ -167,6 +215,7 @@ private:
static std::map <LLUUID, std::vector<fsMotionState>> sMotionStates;
static std::map<LLUUID, int> sCaptureOrder;
static std::map<LLUUID, bool> sMotionStatesOwnedByMe;
};
#endif // LL_FSPoseState_H

View File

@ -208,7 +208,7 @@ void FSPosingMotion::setJointState(LLJoint* joint, U32 state)
FSJointPose* FSPosingMotion::getJointPoseByJointName(const std::string& name)
{
if (mJointPoses.size() < 1)
if (name.empty() || mJointPoses.size() < 1)
return nullptr;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
@ -222,6 +222,24 @@ FSJointPose* FSPosingMotion::getJointPoseByJointName(const std::string& name)
return nullptr;
}
FSJointPose* FSPosingMotion::getJointPoseByJointNumber(const S32& number)
{
if (mJointPoses.size() < 1)
return nullptr;
if (number < 0)
return nullptr;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
if (poserJoint_iter->getJointNumber() != number)
continue;
return &*poserJoint_iter;
}
return nullptr;
}
bool FSPosingMotion::currentlyPosingJoint(LLJoint* joint)
{
if (mJointPoses.size() < 1)
@ -270,14 +288,14 @@ void FSPosingMotion::setJointBvhLock(FSJointPose* joint, bool lockInBvh)
joint->zeroBaseRotation(lockInBvh);
}
bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames)
bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::vector<S32> selectedJointNumbers)
{
FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast<FSPosingMotion*>(motionToLoad);
if (!motionToLoadAsFsPosingMotion)
return false;
LLJoint::JointPriority priority = motionToLoad->getPriority();
bool motionIsForAllJoints = selectedJointNames.empty();
bool motionIsForAllJoints = selectedJointNumbers.empty();
LLQuaternion rot;
LLVector3 position, scale;
@ -285,16 +303,18 @@ bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionT
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
std::string jointName = poserJoint_iter->jointName();
S32 jointNumber = poserJoint_iter->getJointNumber();
std::string jointName = poserJoint_iter->jointName();
bool motionIsForThisJoint = selectedJointNames.find(jointName) != std::string::npos;
bool motionIsForThisJoint =
std::find(selectedJointNumbers.begin(), selectedJointNumbers.end(), jointNumber) != selectedJointNumbers.end();
if (!motionIsForAllJoints && !motionIsForThisJoint)
continue;
hasRotation = hasPosition = hasScale = false;
motionToLoadAsFsPosingMotion->getJointStateAtTime(jointName, timeToLoadAt, &hasRotation, &rot, &hasPosition, &position, &hasScale, &scale);
if (hasRotation)
if (hasRotation && !poserJoint_iter->userHasSetBaseRotationToZero())
poserJoint_iter->setBaseRotation(rot, priority);
if (hasPosition)
@ -337,16 +357,17 @@ void FSPosingMotion::getJointStateAtTime(std::string jointPoseName, F32 timeToLo
}
}
bool FSPosingMotion::otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames)
bool FSPosingMotion::otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::vector<S32> recapturedJointNumbers)
{
FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast<FSPosingMotion*>(motionToQuery);
if (!motionToLoadAsFsPosingMotion)
return false;
return motionToLoadAsFsPosingMotion->motionAnimatesJoints(recapturedJointNames);
return motionToLoadAsFsPosingMotion->motionAnimatesJoints(recapturedJointNumbers);
}
bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames)
// Do not try to access FSPosingMotion state; you are a LLKeyframeMotion cast as a FSPosingMotion, NOT an FSPosingMotion.
bool FSPosingMotion::motionAnimatesJoints(std::vector<int> recapturedJointNumbers)
{
if (mJointMotionList == nullptr)
return false;
@ -354,7 +375,9 @@ bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames)
for (U32 i = 0; i < mJointMotionList->getNumJointMotions(); i++)
{
JointMotion* jm = mJointMotionList->getJointMotion(i);
if (recapturedJointNames.find(jm->mJointName) == std::string::npos)
LLJoint* joint = mCharacter->getJoint(jm->mJointName);
if (std::find(recapturedJointNumbers.begin(), recapturedJointNumbers.end(), joint->getJointNum()) == recapturedJointNumbers.end())
continue;
if (jm->mRotationCurve.mNumKeys > 0)
@ -364,16 +387,15 @@ bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames)
return false;
}
void FSPosingMotion::resetBonePriority(std::string boneNamesToReset)
void FSPosingMotion::resetBonePriority(std::vector<S32> boneNumbersToReset)
{
if (boneNamesToReset.empty())
return;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
for (S32 boneNumber : boneNumbersToReset)
{
std::string jointName = poserJoint_iter->jointName();
if (boneNamesToReset.find(jointName) != std::string::npos)
poserJoint_iter->setJointPriority(LLJoint::LOW_PRIORITY);
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
if (poserJoint_iter->getJointNumber() == boneNumber)
poserJoint_iter->setJointPriority(LLJoint::LOW_PRIORITY);
}
}
}

View File

@ -108,6 +108,13 @@ public:
/// <returns>The matching joint pose, if found, otherwise null.</returns>
FSJointPose* getJointPoseByJointName(const std::string& name);
/// <summary>
/// Gets the joint pose by its LLJoint number.
/// </summary>
/// <param name="number">The number of the joint to get the pose for.</param>
/// <returns>The matching joint pose, if found, otherwise null.</returns>
FSJointPose* getJointPoseByJointNumber(const S32& number);
/// <summary>
/// Gets the motion identity for this animation.
/// </summary>
@ -139,9 +146,9 @@ public:
/// </summary>
/// <param name="motionToLoad">The motion whose joint rotations (etc) we want to copy to this.</param>
/// <param name="timeToLoadAt">The play-time the animation should be advanced to derive the correct joint state.</param>
/// <param name="selectedJointNames">If only some of the joints should be animated by this motion, name them here.</param>
/// <param name="selectedJointNumbers">If only some of the joints should be animated by this motion, number them here.</param>
/// <returns></returns>
bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames);
bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::vector<S32> selectedJointNumbers);
/// <summary>
/// Tries to get the rotation, position and scale for the supplied joint name at the supplied time.
@ -164,27 +171,27 @@ public:
/// <summary>
/// Resets the bone priority to zero for the joints named in the supplied string.
/// </summary>
/// <param name="boneNamesToReset">The string containg bone names (like mPelvis).</param>
void resetBonePriority(std::string boneNamesToReset);
/// <param name="boneNumbersToReset">The vector containing bone numbers.</param>
void resetBonePriority(std::vector<S32> boneNumbersToReset);
/// <summary>
/// Queries whether the supplied motion animates any of the joints named in the supplied string.
/// </summary>
/// <param name="motionToQuery">The motion to query.</param>
/// <param name="recapturedJointNames">A string containing all of the joint names.</param>
/// <param name="recapturedJointNumbers">A string containing all of the joint numbers.</param>
/// <returns>True if the motion animates any of the bones named, otherwise false.</returns>
bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames);
bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::vector<S32> recapturedJointNumbers);
/// <summary>
/// Queries whether the this motion animates any of the joints named in the supplied string.
/// </summary>
/// <param name="recapturedJointNames">A string containing all of the joint names.</param>
/// <param name="recapturedJointNames">A vector containing all of the joint numbers this motion animates.</param>
/// <returns>True if the motion animates any of the bones named, otherwise false.</returns>
/// <remarks>
/// The most significant thing this method does is provide access to protected properties of an LLPosingMotion.
/// Thus its most common usage would be to access those properties for an arbitrary animation.
/// </remarks>
bool motionAnimatesJoints(std::string recapturedJointNames);
bool motionAnimatesJoints(std::vector<S32> recapturedJointNumbers);
private:
/// <summary>

View File

@ -272,7 +272,8 @@ public:
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 ); };
void setJoint(LLJoint* joint) { mManip->setJoint(joint); };
void setReferenceFrame(E_PoserReferenceFrame frame) { mManip->setReferenceFrame(frame); };
// Optional override if you have SHIFT/CTRL combos
virtual LLTool* getOverrideTool(MASK mask) override;

View File

@ -83,8 +83,6 @@ width="430">
<string name="joint_transform_mFaceCheekLowerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<string name="joint_transform_mFaceCheekUpperRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<string name="joint_transform_mFaceCheekLowerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<string name="joint_transform_mFaceLipUpperLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<string name="joint_transform_mFaceLipUpperRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<string name="joint_transform_mHandThumb1Left" translate="false">SWAP_ROLL_AND_PITCH</string>
<string name="joint_transform_mHandThumb1Right" translate="false">SWAP_ROLL_AND_PITCH NEGATE_PITCH</string>
<string name="joint_transform_mHandThumb2Left" translate="false">SWAP_ROLL_AND_PITCH</string>
@ -160,6 +158,105 @@ width="430">
<string name="joint_transform_mFaceForeheadCenter" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<string name="joint_transform_mFaceForeheadRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_PITCH</string>
<!-- Begins with joint_transform_ then has an internal joint name, ONE swap choice of:
SWAP_NOTHING, SWAP_YAW_AND_ROLL, SWAP_YAW_AND_PITCH , SWAP_ROLL_AND_PITCH, SWAP_X2Y_Y2Z_Z2X, SWAP_X2Z_Y2X_Z2Y -->
<string name="joint_frame_mPelvis" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mTorso" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mChest" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mNeck" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHead" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mCollarLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mShoulderLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mElbowLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mWristLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mCollarRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mShoulderRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mElbowRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mWristRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mHipLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mKneeLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mAnkleLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mFootLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mToeLeft" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mHipRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mKneeRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mAnkleRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mFootRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mToeRight" translate="false">NEGATE_ROLL NEGATE_PITCH</string>
<string name="joint_frame_mHandThumb1Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandThumb1Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandThumb2Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandThumb2Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandThumb3Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandThumb3Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandIndex1Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandIndex1Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandIndex2Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandIndex2Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandIndex3Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandIndex3Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandMiddle1Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandMiddle1Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandMiddle2Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandMiddle2Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandMiddle3Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandMiddle3Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandRing1Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandRing1Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandRing2Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandRing2Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandRing3Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandRing3Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandPinky1Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandPinky1Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandPinky2Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandPinky2Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandPinky3Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mHandPinky3Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mEyeRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mEyeLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceTeethLower" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceTeethUpper" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipCornerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipCornerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipUpperLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipUpperCenter" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipUpperRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipLowerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipLowerCenter" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceLipLowerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyeLidUpperLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyeLidUpperRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyecornerInnerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyecornerInnerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyeLidLowerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyeLidLowerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyebrowOuterLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyebrowOuterRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyebrowCenterLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyebrowCenterRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyebrowInnerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEyebrowInnerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceCheekUpperLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceCheekLowerLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceCheekUpperRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceCheekLowerRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceTongueBase" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceTongueTip" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEar1Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEar2Left" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEar1Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceEar2Right" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceNoseBase" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceNoseBridge" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceNoseLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceNoseCenter" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceNoseRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceForeheadLeft" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceForeheadCenter" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceForeheadRight" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<string name="joint_frame_mFaceJaw" translate="false">SWAP_YAW_AND_ROLL NEGATE_ALL</string>
<!-- For the joints/bones/thingos that are apt to Gimbal Lock, we may instead apply rotations as deltas by default, which never lock -->
<string name="joint_delta_rotate_mHipLeft">true</string>
<string name="joint_delta_rotate_mHipRight">true</string>
@ -416,7 +513,7 @@ width="430">
layout="topleft"
mouse_opaque="false"
left="5"
name="title"
name="move_tab_panel"
top="0"
width="235">
<text follows="left|top"
@ -873,7 +970,7 @@ width="430">
name="refresh_avatars"
tool_tip="Refresh the list of avatars and animeshes"
width="20"
top="232"
top="221"
left="3">
<button.commit_callback
function="Poser.RefreshAvatars"/>
@ -894,7 +991,7 @@ width="430">
flash_color="0.7 0.7 1 1"
button_flash_count="64"
button_flash_rate="0.5"
tool_tip="Start posing the selected avatar or animesh, if you are allowed to"
tool_tip="Start posing the selected avatar or animesh, if you are allowed to."
name="start_stop_posing_button"
width="150">
<button.commit_callback
@ -1018,7 +1115,7 @@ width="430">
label="Show joint markers"
follows="left|top"
left="5"
tool_tip="Show small indicators to aid joint selection when visually posing."
tool_tip="Show small indicators to aid joint selection when posing."
top_pad="5"
width="134" />
<check_box
@ -1627,30 +1724,108 @@ width="430">
<button.commit_callback
function="Poser.ToggleSympatheticChanges"/>
</button>
<button
follows="left|top"
height="18"
is_toggle="true"
layout="topleft"
label="W"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
button_flash_enable="true"
flash_color="0.7 0.7 1 1"
button_flash_count="64"
button_flash_rate="0.5"
name="poser_world_frame_toggle"
left_pad="1"
left="2"
top_pad="0"
tool_tip="Make rotational changes relative to World"
width="18" >
<button.commit_callback
function="Poser.ToggleRotationFrame"/>
</button>
<button
follows="left|top"
height="18"
is_toggle="true"
layout="topleft"
label="A"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
button_flash_enable="true"
flash_color="0.7 0.7 1 1"
button_flash_count="64"
button_flash_rate="0.5"
name="poser_avatar_frame_toggle"
left_pad="0"
top_delta="0"
tool_tip="Make rotational changes relative to Avatar"
width="15" >
<button.commit_callback
function="Poser.ToggleRotationFrame"/>
</button>
<button
follows="left|top"
height="18"
is_toggle="true"
layout="topleft"
label="S"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
button_flash_enable="true"
flash_color="0.7 0.7 1 1"
button_flash_count="64"
button_flash_rate="0.5"
name="poser_screen_frame_toggle"
left_pad="0"
top_delta="0"
tool_tip="Make rotational changes relative to Screen"
width="15" >
<button.commit_callback
function="Poser.ToggleRotationFrame"/>
</button>
<button
follows="left|top"
height="18"
layout="topleft"
label="Copy L &gt; R"
name="button_symmetrize_left_to_right"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
button_flash_enable="true"
flash_color="0.7 0.7 1 1"
button_flash_count="64"
button_flash_rate="0.5"
tool_tip="Click to copy change from left side to right side."
left="14"
top_pad="0"
width="70" >
left_pad="1"
top_delta="0"
width="67" >
<button.commit_callback
function="Poser.Symmetrize"
parameter="1"/>
</button>
<button
follows="left|top"
height="19"
height="18"
layout="topleft"
label="Copy R &gt; L"
name="button_symmetrize_right_to_left"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
button_flash_enable="true"
flash_color="0.7 0.7 1 1"
button_flash_count="64"
button_flash_rate="0.5"
tool_tip="Click to copy change from right side to left side."
left_pad="23"
left_pad="1"
top_delta="0"
width="70" >
width="67" >
<button.commit_callback
function="Poser.Symmetrize"
parameter="2"/>
@ -1795,7 +1970,7 @@ width="430">
left_pad="0"
top_delta="0"
name="button_spacer_panel"
width="80"/>
width="82"/>
<button
follows="left|top"
height="21"