diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 71b66546f0..c095df1546 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -150,6 +150,7 @@ set(viewer_SOURCE_FILES fslslpreproc.cpp fslslpreprocviewer.cpp fsmaniprotatejoint.cpp + fsmaniptranslatejoint.cpp fsmoneytracker.cpp fsnamelistavatarmenu.cpp fsnearbychatbarlistener.cpp @@ -997,6 +998,7 @@ set(viewer_HEADER_FILES fslslpreprocviewer.h fsmoneytracker.h fsmaniprotatejoint.h + fsmaniptranslatejoint.h fsnamelistavatarmenu.h fsnearbychatbarlistener.h fsnearbychatcontrol.h diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index a09faa3ed6..77330b1147 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -276,10 +276,8 @@ void FSFloaterPoser::onFocusReceived() void FSFloaterPoser::onFocusLost() { - if( LLEditMenuHandler::gEditMenuHandler == this ) - { + if (LLEditMenuHandler::gEditMenuHandler == this) LLEditMenuHandler::gEditMenuHandler = nullptr; - } } void FSFloaterPoser::draw() @@ -361,22 +359,33 @@ void FSFloaterPoser::enableVisualManipulators() } if (LLToolMgr::getInstance()->getCurrentToolset() != gCameraToolset) - { mLastToolset = LLToolMgr::getInstance()->getCurrentToolset(); - } LLToolMgr::getInstance()->setCurrentToolset(gPoserToolset); LLToolMgr::getInstance()->getCurrentToolset()->selectTool(FSToolCompPose::getInstance()); - FSToolCompPose::getInstance()->setAvatar(gAgentAvatarp); + + LLVOAvatar* avatar = getUiSelectedAvatar(); + setVisualManipulators(avatar); } void FSFloaterPoser::disableVisualManipulators() { if (mLastToolset) - { LLToolMgr::getInstance()->setCurrentToolset(mLastToolset); - } - FSToolCompPose::getInstance()->setAvatar(nullptr); + + setVisualManipulators(nullptr); +} + +void FSFloaterPoser::setVisualManipulators(LLVOAvatar* avatar) +{ + FSToolCompPose::getInstance()->setAvatar(avatar); + FSToolCompPoseTranslate::getInstance()->setAvatar(avatar); + + if (!avatar) + return; + + auto selectedJoints = getUiSelectedPoserJoints(); + updateManipWithFirstSelectedJoint(selectedJoints); } void FSFloaterPoser::onClose(bool app_quitting) @@ -1657,6 +1666,7 @@ void FSFloaterPoser::onToggleRotationFrameButton(const LLUICtrl* toggleButton) } FSToolCompPose::getInstance()->setReferenceFrame(getReferenceFrame()); + FSToolCompPoseTranslate::getInstance()->setReferenceFrame(getReferenceFrame()); refreshRotationSlidersAndSpinners(); } @@ -1949,9 +1959,15 @@ void FSFloaterPoser::updateManipWithFirstSelectedJoint(const std::vector= 1) && (haveImplicitPermission || iCanPoseThem)) + { FSToolCompPose::getInstance()->setJoint(avatarp->getJoint(joints[0]->jointName())); + FSToolCompPoseTranslate::getInstance()->setJoint(avatarp->getJoint(joints[0]->jointName())); + } else + { FSToolCompPose::getInstance()->setJoint(nullptr); + FSToolCompPoseTranslate::getInstance()->setJoint(nullptr); + } } E_RotationStyle FSFloaterPoser::getUiSelectedBoneRotationStyle(const std::string& jointName) const @@ -2476,9 +2492,15 @@ void FSFloaterPoser::onAvatarSelect() bool haveExplicitPermission = havePermissionToAnimateOtherAvatar(avatar); // as permissions allow if (haveImplicitPermission || haveExplicitPermission) + { FSToolCompPose::getInstance()->setAvatar(avatar); + FSToolCompPoseTranslate::getInstance()->setAvatar(avatar); + } else + { FSToolCompPose::getInstance()->setAvatar(nullptr); + FSToolCompPoseTranslate::getInstance()->setAvatar(nullptr); + } bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar); diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index d6af0c882e..9a94ed202d 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -252,6 +252,7 @@ public: // visual manipulators control void enableVisualManipulators(); void disableVisualManipulators(); + void setVisualManipulators(LLVOAvatar* avatar); // Visual cue for which bone is under the mouse-cursor void drawOnHoverJointHint(); diff --git a/indra/newview/fsjointpose.cpp b/indra/newview/fsjointpose.cpp index 2d8f634c45..5860011006 100644 --- a/indra/newview/fsjointpose.cpp +++ b/indra/newview/fsjointpose.cpp @@ -55,47 +55,49 @@ void FSJointPose::setPublicPosition(const LLVector3& pos) { addStateToUndo(mCurrentState); mCurrentState.mPosition.set(pos); - mCurrentState.mLastChangeWasRotational = false; + mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT; } -void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot) +void FSJointPose::setPublicRotation(bool zeroBase, E_PoserChangeType changeType, const LLQuaternion& rot) { addStateToUndo(mCurrentState); if (zeroBase) zeroBaseRotation(true); else - mCurrentState.mUserSpecifiedBaseZero = false; + mCurrentState.mUserSpecifiedBaseZero = POSER_CHANGE_DEFAULT; mCurrentState.mRotation.set(rot); - mCurrentState.mLastChangeWasRotational = true; + mCurrentState.mPoserChangeType = changeType; } void FSJointPose::setPublicScale(const LLVector3& scale) { addStateToUndo(mCurrentState); mCurrentState.mScale.set(scale); - mCurrentState.mLastChangeWasRotational = false; + mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT; } -bool FSJointPose::undoLastChange() +E_PoserChangeType FSJointPose::undoLastChange() { - bool changeType = mCurrentState.mLastChangeWasRotational; + E_PoserChangeType changeType = mCurrentState.mPoserChangeType; mCurrentState = undoLastStateChange(mCurrentState); return changeType; } -void FSJointPose::redoLastChange() +E_PoserChangeType FSJointPose::redoLastChange() { mCurrentState = redoLastStateChange(mCurrentState); + + return mCurrentState.mPoserChangeType; } void FSJointPose::resetJoint() { addStateToUndo(mCurrentState); mCurrentState.resetJoint(); - mCurrentState.mLastChangeWasRotational = true; + mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT; } void FSJointPose::addStateToUndo(const FSJointState& stateToAddToUndo) @@ -169,13 +171,13 @@ void FSJointPose::recaptureJoint() } mCurrentState = FSJointState(joint); - mCurrentState.mLastChangeWasRotational = true; + mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT; } LLQuaternion FSJointPose::updateJointAsDelta(bool zeroBase, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale) { addStateToUndo(mCurrentState); - mCurrentState.mLastChangeWasRotational = true; + mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT; return mCurrentState.updateFromJointProperties(zeroBase, rotation, position, scale); } @@ -231,7 +233,7 @@ void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint) addStateToUndo(FSJointState(mCurrentState)); mCurrentState.cloneRotationFrom(fromJoint->mCurrentState); - mCurrentState.mLastChangeWasRotational = true; + mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT; } void FSJointPose::mirrorRotationFrom(FSJointPose* fromJoint) diff --git a/indra/newview/fsjointpose.h b/indra/newview/fsjointpose.h index bc551afbdf..64c9fdc05e 100644 --- a/indra/newview/fsjointpose.h +++ b/indra/newview/fsjointpose.h @@ -32,6 +32,17 @@ //----------------------------------------------------------------------------- #include "llmotion.h" +/// +/// Describes the kind of pose-change. +/// Useful for undoing complex changes. +/// +typedef enum E_PoserChangeType +{ + POSER_CHANGE_DEFAULT = 0, // a simple change to pos/rot/scale + POSER_CHANGE_CHILDMOVE = 1, // position change of a child by rotating its 2 antecedent joints + POSER_CHANGE_ROTATION = 2, // a rotation change +} E_PoserChangeType; + //----------------------------------------------------------------------------- // class FSJointPose //----------------------------------------------------------------------------- @@ -70,13 +81,14 @@ class FSJointPose /// /// Undoes the last position set, if any. /// - /// true if the change we un-did was rotational. - bool undoLastChange(); + /// A value indicating what kind of change was undone (important for undo). + E_PoserChangeType undoLastChange(); /// /// Undoes the last position set, if any. /// - void redoLastChange(); + /// A value indicating what kind of change was redone. + E_PoserChangeType redoLastChange(); /// /// Resets the joint to its conditions when posing started. @@ -100,7 +112,7 @@ class FSJointPose /// 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. /// - void setPublicRotation(bool zeroBase, const LLQuaternion& rot); + void setPublicRotation(bool zeroBase, E_PoserChangeType changeType, const LLQuaternion& rot); /// /// Reflects the base and delta rotation of the represented joint left-right. @@ -319,7 +331,7 @@ class FSJointPose mUserSpecifiedBaseZero = false; mRotationIsWorldLocked = false; mJointRotationIsMirrored = false; - mLastChangeWasRotational = true; + mPoserChangeType = POSER_CHANGE_DEFAULT; mBaseRotation.set(mStartingRotation); mRotation.set(LLQuaternion::DEFAULT); mPosition.setZero(); @@ -417,15 +429,15 @@ class FSJointPose mRotationIsWorldLocked = state->mRotationIsWorldLocked; mBasePriority = state->mBasePriority; mJointRotationIsMirrored = state->mJointRotationIsMirrored; - mLastChangeWasRotational = state->mLastChangeWasRotational; + mPoserChangeType = state->mPoserChangeType; } public: - LLQuaternion mRotation; - LLVector3 mPosition; - LLVector3 mScale; - bool mRotationIsWorldLocked = false; - bool mLastChangeWasRotational = false; + LLQuaternion mRotation; + LLVector3 mPosition; + LLVector3 mScale; + bool mRotationIsWorldLocked = false; + E_PoserChangeType mPoserChangeType = POSER_CHANGE_DEFAULT; /// /// Whether the joint has been mirrored. diff --git a/indra/newview/fsmaniptranslatejoint.cpp b/indra/newview/fsmaniptranslatejoint.cpp new file mode 100644 index 0000000000..155a22523a --- /dev/null +++ b/indra/newview/fsmaniptranslatejoint.cpp @@ -0,0 +1,1255 @@ +/** + * @file fsmaniptranslatejoint.cpp + * @brief custom manipulator for moving joints + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (c) 2025 Angeldark Raymaker @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#include "fsmaniptranslatejoint.h" +#include "llgl.h" +#include "llrender.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "llcylinder.h" +#include "llfloatertools.h" +#include "llfontgl.h" +#include "llhudrender.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llviewercamera.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llui.h" +#include "lltrans.h" +#include "fsfloaterposer.h" +#include "llfloaterreg.h" + +const S32 NUM_AXES = 3; +const S32 MOUSE_DRAG_SLOP = 2; // pixels +const F32 SELECTED_ARROW_SCALE = 1.3f; +const F32 MANIPULATOR_HOTSPOT_START = 0.2f; +const F32 MANIPULATOR_HOTSPOT_END = 1.2f; +const F32 MIN_PLANE_MANIP_DOT_PRODUCT = 0.25f; +const F32 PLANE_TICK_SIZE = 0.4f; + +static LLPointer sGridTex = NULL ; + +const LLManip::EManipPart MANIPULATOR_IDS[9] = +{ + LLManip::LL_X_ARROW, + LLManip::LL_Y_ARROW, + LLManip::LL_Z_ARROW, + LLManip::LL_X_ARROW, + LLManip::LL_Y_ARROW, + LLManip::LL_Z_ARROW, + LLManip::LL_YZ_PLANE, + LLManip::LL_XZ_PLANE, + LLManip::LL_XY_PLANE +}; + +const U32 ARROW_TO_AXIS[4] = +{ + VX, + VX, + VY, + VZ +}; + +// Sort manipulator handles by their screen-space projection +struct ClosestToCamera +{ + bool operator()(const FSManipTranslateJoint::ManipulatorHandle& a, + const FSManipTranslateJoint::ManipulatorHandle& b) const + { + return a.mEndPosition.mV[VZ] < b.mEndPosition.mV[VZ]; + } +}; + +FSManipTranslateJoint::FSManipTranslateJoint(LLToolComposite* composite) : + LLManipTranslate(composite), + mLastHoverMouseX(-1), + mLastHoverMouseY(-1), + mMouseOutsideSlop(false), + mCopyMadeThisDrag(false), + mMouseDownX(-1), + mMouseDownY(-1), + mAxisArrowLength(50), + mConeSize(0), + mArrowLengthMeters(0.f), + mPlaneManipOffsetMeters(0.f), + mUpdateTimer(), + mSnapOffsetMeters(0.f), + mArrowScales(1.f, 1.f, 1.f), + mPlaneScales(1.f, 1.f, 1.f), + mPlaneManipPositions(1.f, 1.f, 1.f, 1.f) +{ + if (sGridTex.isNull()) + { + restoreGL(); + } +} + +void FSManipTranslateJoint::setAvatar(LLVOAvatar* avatar) +{ + mAvatar = avatar; +} + +void FSManipTranslateJoint::setJoint(LLJoint* joint) +{ + mJoint = joint; +} + +//static +U32 FSManipTranslateJoint::getGridTexName() +{ + if(sGridTex.isNull()) + { + restoreGL() ; + } + + return sGridTex.isNull() ? 0 : sGridTex->getTexName() ; +} + +//static +void FSManipTranslateJoint::destroyGL() +{ + if (sGridTex) + { + sGridTex = NULL ; + } +} + +//static +void FSManipTranslateJoint::restoreGL() +{ + //generate grid texture + U32 rez = 512; + U32 mip = 0; + + destroyGL() ; + sGridTex = LLViewerTextureManager::getLocalTexture() ; + if(!sGridTex->createGLTexture()) + { + sGridTex = NULL ; + return ; + } + + GLuint* d = new GLuint[rez*rez]; + + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, sGridTex->getTexName(), true); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR); + + while (rez >= 1) + { + for (U32 i = 0; i < rez*rez; i++) + { + d[i] = 0x00FFFFFF; + } + + U32 subcol = 0xFFFFFFFF; + if (rez >= 4) + { //large grain grid + for (U32 i = 0; i < rez; i++) + { + if (rez <= 16) + { + if (rez == 16) + { + subcol = 0xA0FFFFFF; + } + else if (rez == 8) + { + subcol = 0x80FFFFFF; + } + else + { + subcol = 0x40FFFFFF; + } + } + else + { + subcol = 0xFFFFFFFF; + } + d[i *rez+ 0 ] = subcol; + d[0 *rez+ i ] = subcol; + if (rez >= 32) + { + d[i *rez+ (rez-1)] = subcol; + d[(rez-1) *rez+ i ] = subcol; + } + + if (rez >= 64) + { + subcol = 0xFFFFFFFF; + + if (i > 0 && i < (rez-1)) + { + d[i *rez+ 1 ] = subcol; + d[i *rez+ (rez-2)] = subcol; + d[1 *rez+ i ] = subcol; + d[(rez-2) *rez+ i ] = subcol; + } + } + } + } + + subcol = 0x50A0A0A0; + if (rez >= 128) + { //small grain grid + for (U32 i = 8; i < rez; i+=8) + { + for (U32 j = 2; j < rez-2; j++) + { + d[i *rez+ j] = subcol; + d[j *rez+ i] = subcol; + } + } + } + if (rez >= 64) + { //medium grain grid + if (rez == 64) + { + subcol = 0x50A0A0A0; + } + else + { + subcol = 0xA0D0D0D0; + } + + for (U32 i = 32; i < rez; i+=32) + { + U32 pi = i-1; + for (U32 j = 2; j < rez-2; j++) + { + d[i *rez+ j] = subcol; + d[j *rez+ i] = subcol; + + if (rez > 128) + { + d[pi *rez+ j] = subcol; + d[j *rez+ pi] = subcol; + } + } + } + } + LLImageGL::setManualImage(GL_TEXTURE_2D, mip, GL_RGBA, rez, rez, GL_RGBA, GL_UNSIGNED_BYTE, d); + rez = rez >> 1; + mip++; + } + delete [] d; +} + +void FSManipTranslateJoint::handleSelect() +{ + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + if (gFloaterTools) + { + gFloaterTools->setStatusText("move"); + } + LLManip::handleSelect(); +} + +bool FSManipTranslateJoint::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // didn't click in any UI object, so must have clicked in the world + if( (mHighlightedPart == LL_X_ARROW || + mHighlightedPart == LL_Y_ARROW || + mHighlightedPart == LL_Z_ARROW || + mHighlightedPart == LL_YZ_PLANE || + mHighlightedPart == LL_XZ_PLANE || + mHighlightedPart == LL_XY_PLANE ) ) + { + handled = handleMouseDownOnPart( x, y, mask ); + } + + return handled; +} + +// Assumes that one of the arrows on an object was hit. +bool FSManipTranslateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask) +{ + bool can_move = canAffectSelection(); + if (!can_move) + { + return false; + } + + highlightManipulators(x, y); + S32 hit_part = mHighlightedPart; + + if( (hit_part != LL_X_ARROW) && + (hit_part != LL_Y_ARROW) && + (hit_part != LL_Z_ARROW) && + (hit_part != LL_YZ_PLANE) && + (hit_part != LL_XZ_PLANE) && + (hit_part != LL_XY_PLANE) ) + { + return true; + } + + mHelpTextTimer.reset(); + sNumTimesHelpTextShown++; + + LLSelectMgr::getInstance()->getGrid(mGridOrigin, mGridRotation, mGridScale); + + mManipPart = (EManipPart)hit_part; + mMouseDownX = x; + mMouseDownY = y; + mMouseOutsideSlop = false; + + LLVector3 axis; + + if (!isAvatarJointSafeToUse()) + { + LL_WARNS() << "Translate manip joint lost the joint or avatar." << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + + auto* poser = LLFloaterReg::findTypedInstance("fs_poser"); + if (!poser) + return false; + + poser->setFocus(true); + + // Compute unit vectors for arrow hit and a plane through that vector + bool axis_exists = getManipJointAxis(mJoint, mManipPart, axis); + getManipJointNormal(mJoint, mManipPart, mManipNormal); + + LLVector3 select_center_agent = mJoint->getWorldPosition(); + + // if we clicked on a planar manipulator, recenter mouse cursor + if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE) + { + LLCoordGL mouse_pos; + if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(select_center_agent, mouse_pos)) + { + // mouse_pos may be nonsense + LL_WARNS() << "Failed to project object center to screen" << LL_ENDL; + } + else if (gSavedSettings.getBOOL("SnapToMouseCursor")) + { + LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY); + x = mouse_pos.mX; + y = mouse_pos.mY; + } + } + + LLVector3d object_start_global = gAgent.getPosGlobalFromAgent(mJoint->getWorldPosition()); + getMousePointOnPlaneGlobal(mDragCursorStartGlobal, x, y, object_start_global, mManipNormal); + mDragSelectionStartGlobal = object_start_global; + mDragCursorLastGlobal.setZero(); + + // Route future Mouse messages here preemptively. (Release on mouse up.) + setMouseCapture(true); + + return true; +} + +bool FSManipTranslateJoint::handleHover(S32 x, S32 y, MASK mask) +{ + // Translation tool only works if mouse button is down. + // Bail out if mouse not down. + if (!hasMouseCapture()) + { + LL_DEBUGS("UserInput") << "hover handled by FSManipTranslateJoint (inactive)" << LL_ENDL; + // Always show cursor + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + + highlightManipulators(x, y); + return true; + } + + // Handle auto-rotation if necessary. + LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); + const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD; + const S32 ROTATE_H_MARGIN = world_rect.getWidth() / 20; + const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped; + bool rotated = false; + + // ...moving joints can move camera about focus point + if (x < ROTATE_H_MARGIN) + { + gAgentCamera.cameraOrbitAround(rotate_angle); + rotated = true; + } + else if (x > world_rect.getWidth() - ROTATE_H_MARGIN) + { + gAgentCamera.cameraOrbitAround(-rotate_angle); + rotated = true; + } + + // Suppress processing if mouse hasn't actually moved. + // This may cause problems if the camera moves outside of the + // rotation above. + if (x == mLastHoverMouseX && y == mLastHoverMouseY && !rotated) + { + LL_DEBUGS("UserInput") << "hover handled by FSManipTranslateJoint (mouse unmoved)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + mLastHoverMouseX = x; + mLastHoverMouseY = y; + + // Suppress if mouse hasn't moved past the initial slop region + // Reset once we start moving + if (!mMouseOutsideSlop) + { + if (abs(mMouseDownX - x) < MOUSE_DRAG_SLOP && abs(mMouseDownY - y) < MOUSE_DRAG_SLOP ) + { + LL_DEBUGS("UserInput") << "hover handled by FSManipTranslateJoint (mouse inside slop)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + else + { + // ...just went outside the slop region + mMouseOutsideSlop = true; + } + } + + // Throttle updates to 10 per second. + LLVector3 axis_f; + LLVector3d axis_d; + + if (!isAvatarJointSafeToUse()) + { + LL_WARNS() << "Translate joint manip lost the joint or avatar" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + + // Compute unit vectors for arrow hit and a plane through that vector + bool axis_exists = getManipJointAxis(mJoint, mManipPart, axis_f); + axis_d.setVec(axis_f); + + LLVector3d current_pos_global = gAgent.getPosGlobalFromAgent(mJoint->getWorldPosition()); + + // Project the cursor onto that plane + LLVector3d relative_move; + getMousePointOnPlaneGlobal(relative_move, x, y, current_pos_global, mManipNormal); + relative_move -= mDragCursorStartGlobal; + + F64 axis_magnitude = relative_move * axis_d; // dot product + LLVector3d cursor_point_snap_line; + F64 off_axis_magnitude; + + getMousePointOnPlaneGlobal(cursor_point_snap_line, x, y, current_pos_global, mSnapOffsetAxis % axis_f); + off_axis_magnitude = axis_exists ? llabs((cursor_point_snap_line - current_pos_global) * LLVector3d(mSnapOffsetAxis)) : 0.f; + + // Clamp to arrow direction + // *FIX: does this apply anymore? + if (!axis_exists) + { + axis_magnitude = relative_move.normVec(); + axis_d.setVec(relative_move); + axis_d.normVec(); + axis_f.setVec(axis_d); + } + + LLVector3d clamped_relative_move = axis_magnitude * axis_d; // scalar multiply + LLVector3 clamped_relative_move_f = (F32)axis_magnitude * axis_f; // scalar multiply + auto* poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); + if (poser && mJoint) + poser->updatePosedBones(mJoint->getName(), LLQuaternion(), getChangeInPosition(clamped_relative_move_f), LLVector3::zero); + + mDragCursorLastGlobal = clamped_relative_move_f; + gAgentCamera.clearFocusObject(); + + LL_DEBUGS("UserInput") << "hover handled by FSManipTranslateJoint (active)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + + return true; +} + +LLVector3 FSManipTranslateJoint::getChangeInPosition(LLVector3 newPosition) const +{ + LLVector3 rawChange = newPosition - mDragCursorLastGlobal; + + switch (mManipPart) + { + case LL_X_ARROW: + rawChange[VY] = 0.f; + rawChange[VZ] = 0.f; + break; + case LL_Y_ARROW: + rawChange[VX] = 0.f; + rawChange[VZ] = 0.f; + break; + case LL_Z_ARROW: + rawChange[VX] = 0.f; + rawChange[VY] = 0.f; + break; + case LL_XY_PLANE: + rawChange[VZ] = 0.f; + break; + case LL_XZ_PLANE: + rawChange[VY] = 0.f; + break; + case LL_YZ_PLANE: + rawChange[VX] = 0.f; + break; + break; + default: + break; + } + + return rawChange; +} + +void FSManipTranslateJoint::highlightManipulators(S32 x, S32 y) +{ + mHighlightedPart = LL_NO_PART; + + if (!isAvatarJointSafeToUse()) + return; + + LLMatrix4 projMatrix = LLViewerCamera::getInstance()->getProjection(); + LLMatrix4 modelView = LLViewerCamera::getInstance()->getModelview(); + + LLVector3 object_position = mJoint->getWorldPosition(); + + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + LLVector3 relative_camera_dir; + + LLMatrix4 transform; + relative_camera_dir = (object_position - LLViewerCamera::getInstance()->getOrigin()) * ~grid_rotation; + relative_camera_dir.normVec(); + + transform.initRotTrans(grid_rotation, LLVector4(object_position)); + transform *= modelView; + transform *= projMatrix; + + S32 numManips = 0; + + // edges + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 1.f); + + S32 num_arrow_manips = numManips; + + // planar manipulators + bool planar_manip_yz_visible = false; + bool planar_manip_xz_visible = false; + bool planar_manip_xy_visible = false; + + mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + if (llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + planar_manip_yz_visible = true; + } + + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + if (llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + planar_manip_xz_visible = true; + } + + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + if (llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + planar_manip_xy_visible = true; + } + + // Project up to 9 manipulators to screen space 2*X, 2*Y, 2*Z, 3*planes + std::vector projected_manipulators; + projected_manipulators.reserve(9); + + for (S32 i = 0; i < num_arrow_manips; i+= 2) + { + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 10.f); // 10 pixel hotspot for arrows + projected_manipulators.push_back(projected_manip); + } + + if (planar_manip_yz_visible) + { + S32 i = num_arrow_manips; + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 20.f); // 20 pixels for planar manipulators + projected_manipulators.push_back(projected_manip); + } + + if (planar_manip_xz_visible) + { + S32 i = num_arrow_manips + 2; + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 20.f); // 20 pixels for planar manipulators + projected_manipulators.push_back(projected_manip); + } + + if (planar_manip_xy_visible) + { + S32 i = num_arrow_manips + 4; + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 20.f); // 20 pixels for planar manipulators + projected_manipulators.push_back(projected_manip); + } + + LLVector2 manip_start_2d; + LLVector2 manip_end_2d; + LLVector2 manip_dir; + LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); + F32 half_width = (F32)world_view_rect.getWidth() / 2.f; + F32 half_height = (F32)world_view_rect.getHeight() / 2.f; + LLVector2 mousePos((F32)x - half_width, (F32)y - half_height); + LLVector2 mouse_delta; + + // Keep order consistent with insertion via stable_sort + std::stable_sort( projected_manipulators.begin(), + projected_manipulators.end(), + ClosestToCamera() ); + + std::vector::iterator it = projected_manipulators.begin(); + for ( ; it != projected_manipulators.end(); ++it) + { + ManipulatorHandle& manipulator = *it; + { + manip_start_2d.setVec(manipulator.mStartPosition.mV[VX] * half_width, manipulator.mStartPosition.mV[VY] * half_height); + manip_end_2d.setVec(manipulator.mEndPosition.mV[VX] * half_width, manipulator.mEndPosition.mV[VY] * half_height); + manip_dir = manip_end_2d - manip_start_2d; + + mouse_delta = mousePos - manip_start_2d; + + F32 manip_length = manip_dir.normVec(); + + F32 mouse_pos_manip = mouse_delta * manip_dir; + F32 mouse_dist_manip_squared = mouse_delta.magVecSquared() - (mouse_pos_manip * mouse_pos_manip); + + if (mouse_pos_manip > 0.f && + mouse_pos_manip < manip_length && + mouse_dist_manip_squared < manipulator.mHotSpotRadius * manipulator.mHotSpotRadius) + { + mHighlightedPart = manipulator.mManipID; + break; + } + } + } +} + +bool FSManipTranslateJoint::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // first, perform normal processing in case this was a quick-click + handleHover(x, y, mask); + + if (hasMouseCapture()) + { + // make sure arrow colors go back to normal + mManipPart = LL_NO_PART; + LLSelectMgr::getInstance()->enableSilhouette(true); + + // Might have missed last update due to UPDATE_DELAY timing. + LLSelectMgr::getInstance()->sendMultipleUpdate( UPD_POSITION ); + + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + } + + return LLManip::handleMouseUp(x, y, mask); +} + +void FSManipTranslateJoint::render() +{ + if (!isMoveableJoint()) + return; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + renderGuidelines(); + } + { + renderTranslationHandles(); + } + gGL.popMatrix(); + + renderText(); +} + +void FSManipTranslateJoint::renderText() +{ + if (!isAvatarJointSafeToUse()) + return; + + LLVector3 pos = mJoint->getWorldPosition(); + renderXYZ(pos); +} + +void FSManipTranslateJoint::renderTranslationHandles() +{ + LLGLDepthTest gls_depth(GL_FALSE); + if (!isAvatarJointSafeToUse()) + return; + + LLVector3 grid_origin, grid_scale, at_axis; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + at_axis = LLViewerCamera::getInstance()->getAtAxis() * ~grid_rotation; + + if (at_axis.mV[VX] > 0.f) + { + mPlaneManipPositions.mV[VX] = 1.f; + } + else + { + mPlaneManipPositions.mV[VX] = -1.f; + } + + if (at_axis.mV[VY] > 0.f) + { + mPlaneManipPositions.mV[VY] = 1.f; + } + else + { + mPlaneManipPositions.mV[VY] = -1.f; + } + + if (at_axis.mV[VZ] > 0.f) + { + mPlaneManipPositions.mV[VZ] = 1.f; + } + else + { + mPlaneManipPositions.mV[VZ] = -1.f; + } + + LLVector3 selection_center = mJoint->getWorldPosition(); + + // Drag handles + LLVector3 camera_pos_agent = gAgentCamera.getCameraPositionAgent(); + F32 range = dist_vec(camera_pos_agent, selection_center); + F32 range_from_agent = dist_vec(gAgent.getPositionAgent(), selection_center); + + // Don't draw handles if you're too far away + if (gSavedSettings.getBOOL("LimitSelectDistance") && range_from_agent > gSavedSettings.getF32("MaxSelectDistance")) + return; + + if (range > 0.001f) + { + F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians + mArrowLengthMeters = range * tan(apparent_angle); + } + else + { + mArrowLengthMeters = 1.0f; + } + + //Assume that UI scale factor is equivalent for X and Y axis + F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; + mArrowLengthMeters *= ui_scale_factor; + + mPlaneManipOffsetMeters = mArrowLengthMeters * 1.8f; + mConeSize = mArrowLengthMeters / 4.f; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { + gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); + + F32 angle_radians, x, y, z; + grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); + + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + LLQuaternion invRotation = grid_rotation; + invRotation.conjQuat(); + + LLVector3 relative_camera_dir; + relative_camera_dir = (selection_center - LLViewerCamera::getInstance()->getOrigin()) * invRotation; + relative_camera_dir.normVec(); + + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDisable cull_face(GL_CULL_FACE); + + LLColor4 color1; + LLColor4 color2; + + // update manipulator sizes + for (S32 index = 0; index < 3; index++) + { + if (index == mManipPart - LL_X_ARROW || index == mHighlightedPart - LL_X_ARROW) + { + mArrowScales.mV[index] = lerp(mArrowScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + } + else if (index == mManipPart - LL_YZ_PLANE || index == mHighlightedPart - LL_YZ_PLANE) + { + mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + } + else + { + mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + } + } + + if ((mManipPart == LL_NO_PART || mManipPart == LL_YZ_PLANE) && llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + // render YZ plane manipulator + gGL.pushMatrix(); + gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); + gGL.translatef(0.f, mPlaneManipOffsetMeters, mPlaneManipOffsetMeters); + gGL.scalef(mPlaneScales.mV[VX], mPlaneScales.mV[VX], mPlaneScales.mV[VX]); + if (mHighlightedPart == LL_YZ_PLANE) + { + color1.setVec(0.f, 1.f, 0.f, 1.f); + color2.setVec(0.f, 0.f, 1.f, 1.f); + } + else + { + color1.setVec(0.f, 1.f, 0.f, 0.6f); + color2.setVec(0.f, 0.f, 1.f, 0.6f); + } + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4fv(color1.mV); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + + gGL.color4fv(color2.mV); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + } + gGL.end(); + + LLUI::setLineWidth(3.0f); + gGL.begin(LLRender::LINES); + { + gGL.color4f(0.f, 0.f, 0.f, 0.3f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f); + + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + } + gGL.end(); + LLUI::setLineWidth(1.0f); + gGL.popMatrix(); + } + + if ((mManipPart == LL_NO_PART || mManipPart == LL_XZ_PLANE) && llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + // render XZ plane manipulator + gGL.pushMatrix(); + gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); + gGL.translatef(mPlaneManipOffsetMeters, 0.f, mPlaneManipOffsetMeters); + gGL.scalef(mPlaneScales.mV[VY], mPlaneScales.mV[VY], mPlaneScales.mV[VY]); + if (mHighlightedPart == LL_XZ_PLANE) + { + color1.setVec(0.f, 0.f, 1.f, 1.f); + color2.setVec(1.f, 0.f, 0.f, 1.f); + } + else + { + color1.setVec(0.f, 0.f, 1.f, 0.6f); + color2.setVec(1.f, 0.f, 0.f, 0.6f); + } + + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4fv(color1.mV); + gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + + gGL.color4fv(color2.mV); + gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + } + gGL.end(); + + LLUI::setLineWidth(3.0f); + gGL.begin(LLRender::LINES); + { + gGL.color4f(0.f, 0.f, 0.f, 0.3f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f); + + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + } + gGL.end(); + LLUI::setLineWidth(1.0f); + + gGL.popMatrix(); + } + + if ((mManipPart == LL_NO_PART || mManipPart == LL_XY_PLANE) && llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + // render XY plane manipulator + gGL.pushMatrix(); + gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); + +/* Y + ^ + v1 + | \ + |<- v0 + | /| \ + v2__v__v3 > X +*/ + LLVector3 v0,v1,v2,v3; + gGL.translatef(mPlaneManipOffsetMeters, mPlaneManipOffsetMeters, 0.f); + v0 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); + v1 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f); + v2 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); + v3 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); + + gGL.scalef(mPlaneScales.mV[VZ], mPlaneScales.mV[VZ], mPlaneScales.mV[VZ]); + if (mHighlightedPart == LL_XY_PLANE) + { + color1.setVec(1.f, 0.f, 0.f, 1.f); + color2.setVec(0.f, 1.f, 0.f, 1.f); + } + else + { + color1.setVec(0.8f, 0.f, 0.f, 0.6f); + color2.setVec(0.f, 0.8f, 0.f, 0.6f); + } + + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4fv(color1.mV); + gGL.vertex3fv(v0.mV); + gGL.vertex3fv(v1.mV); + gGL.vertex3fv(v2.mV); + + gGL.color4fv(color2.mV); + gGL.vertex3fv(v2.mV); + gGL.vertex3fv(v3.mV); + gGL.vertex3fv(v0.mV); + } + gGL.end(); + + LLUI::setLineWidth(3.0f); + gGL.begin(LLRender::LINES); + { + gGL.color4f(0.f, 0.f, 0.f, 0.3f); + LLVector3 v12 = (v1 + v2) * .5f; + gGL.vertex3fv(v0.mV); + gGL.vertex3fv(v12.mV); + gGL.vertex3fv(v12.mV); + gGL.vertex3fv((v12 + (v0-v12)*.3f + (v2-v12)*.3f).mV); + gGL.vertex3fv(v12.mV); + gGL.vertex3fv((v12 + (v0-v12)*.3f + (v1-v12)*.3f).mV); + + LLVector3 v23 = (v2 + v3) * .5f; + gGL.vertex3fv(v0.mV); + gGL.vertex3fv(v23.mV); + gGL.vertex3fv(v23.mV); + gGL.vertex3fv((v23 + (v0-v23)*.3f + (v3-v23)*.3f).mV); + gGL.vertex3fv(v23.mV); + gGL.vertex3fv((v23 + (v0-v23)*.3f + (v2-v23)*.3f).mV); + } + gGL.end(); + LLUI::setLineWidth(1.0f); + + gGL.popMatrix(); + } + } + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // Since we draw handles with depth testing off, we need to draw them in the + // proper depth order. + + // Copied from LLDrawable::updateGeometry + LLVector3 pos_agent = mJoint->getWorldPosition(); + LLVector3 camera_agent = gAgentCamera.getCameraPositionAgent(); + LLVector3 headPos = pos_agent - camera_agent; + + LLVector3 orientWRTHead = headPos * invRotation; + + // Find nearest vertex + U32 nearest = (orientWRTHead.mV[0] < 0.0f ? 1 : 0) + + (orientWRTHead.mV[1] < 0.0f ? 2 : 0) + + (orientWRTHead.mV[2] < 0.0f ? 4 : 0); + + // opposite faces on Linden cubes: + // 0 & 5 + // 1 & 3 + // 2 & 4 + + // Table of order to draw faces, based on nearest vertex + static U32 face_list[8][NUM_AXES*2] = { + { 2,0,1, 4,5,3 }, // v6 F201 F453 + { 2,0,3, 4,5,1 }, // v7 F203 F451 + { 4,0,1, 2,5,3 }, // v5 F401 F253 + { 4,0,3, 2,5,1 }, // v4 F403 F251 + { 2,5,1, 4,0,3 }, // v2 F251 F403 + { 2,5,3, 4,0,1 }, // v3 F253 F401 + { 4,5,1, 2,0,3 }, // v1 F451 F203 + { 4,5,3, 2,0,1 }, // v0 F453 F201 + }; + static const EManipPart which_arrow[6] = { + LL_Z_ARROW, + LL_X_ARROW, + LL_Y_ARROW, + LL_X_ARROW, + LL_Y_ARROW, + LL_Z_ARROW}; + + // draw arrows for deeper faces first, closer faces last + LLVector3 camera_axis; + camera_axis.setVec(gAgentCamera.getCameraPositionAgent() - mJoint->getWorldPosition()); + + for (U32 i = 0; i < NUM_AXES*2; i++) + { + U32 face = face_list[nearest][i]; + + LLVector3 arrow_axis; + getManipJointAxis(mJoint, which_arrow[face], arrow_axis); + + renderArrow(which_arrow[face], + mManipPart, + (face >= 3) ? -mConeSize : mConeSize, + (face >= 3) ? -mArrowLengthMeters : mArrowLengthMeters, + mConeSize, + false); + } + } + } + gGL.popMatrix(); +} + +void FSManipTranslateJoint::renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction) +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLEnable gls_blend(GL_BLEND); + + for (S32 pass = 1; pass <= 2; pass++) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, pass == 1 ? GL_LEQUAL : GL_GREATER); + gGL.pushMatrix(); + + S32 index = 0; + + index = ARROW_TO_AXIS[which_arrow]; + + // assign a color for this arrow + LLColor4 color; // black + if (which_arrow == selected_arrow || which_arrow == mHighlightedPart) + { + color.mV[index] = (pass == 1) ? 1.f : 0.5f; + } + else if (selected_arrow != LL_NO_PART) + { + color.mV[VALPHA] = 0.f; + } + else + { + color.mV[index] = pass == 1 ? .8f : .35f ; // red, green, or blue + color.mV[VALPHA] = 0.6f; + } + gGL.color4fv( color.mV ); + + LLVector3 vec; + + { + LLUI::setLineWidth(2.0f); + gGL.begin(LLRender::LINES); + vec.mV[index] = box_size; + gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]); + + vec.mV[index] = arrow_size; + gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]); + gGL.end(); + LLUI::setLineWidth(1.0f); + } + + gGL.translatef(vec.mV[0], vec.mV[1], vec.mV[2]); + gGL.scalef(handle_size, handle_size, handle_size); + + F32 rot = 0.0f; + LLVector3 axis; + + switch(which_arrow) + { + case LL_X_ARROW: + rot = reverse_direction ? -90.0f : 90.0f; + axis.mV[1] = 1.0f; + break; + case LL_Y_ARROW: + rot = reverse_direction ? 90.0f : -90.0f; + axis.mV[0] = 1.0f; + break; + case LL_Z_ARROW: + rot = reverse_direction ? 180.0f : 0.0f; + axis.mV[0] = 1.0f; + break; + default: + LL_ERRS() << "renderArrow called with bad arrow " << which_arrow << LL_ENDL; + break; + } + + gGL.diffuseColor4fv(color.mV); + gGL.rotatef(rot, axis.mV[0], axis.mV[1], axis.mV[2]); + gGL.scalef(mArrowScales.mV[index], mArrowScales.mV[index], mArrowScales.mV[index] * 1.5f); + + gCone.render(); + + gGL.popMatrix(); + } +} + +bool FSManipTranslateJoint::isAvatarJointSafeToUse() +{ + if (!mJoint || !mAvatar) + return false; + + if (mAvatar->isDead() || !mAvatar->isFullyLoaded()) + { + setAvatar(nullptr); + return false; + } + + return true; +} + +bool FSManipTranslateJoint::isMoveableJoint() +{ + if (!mJoint) + return false; + + std::string jointName = mJoint->getName(); + if (jointName == "mPelvis" || jointName == "mRoot") + return true; // on the root we want to do a simple translation + + // if the parent or grandparent joints are null or root, we don't want to do anything + LLJoint* parentJoint = mJoint->getParent(); + if (!parentJoint) + return false; + + jointName = parentJoint->getName(); + if (jointName == "mPelvis" || jointName == "mRoot") + return false; + + LLJoint* grandParentJoint = parentJoint->getParent(); + if (!grandParentJoint) + return false; + + jointName = grandParentJoint->getName(); + if (jointName == "mPelvis" || jointName == "mRoot") + return false; + + return true; +} + +// virtual +bool FSManipTranslateJoint::canAffectSelection() +{ + return isAvatarJointSafeToUse(); +} diff --git a/indra/newview/fsmaniptranslatejoint.h b/indra/newview/fsmaniptranslatejoint.h new file mode 100644 index 0000000000..98bfba77c7 --- /dev/null +++ b/indra/newview/fsmaniptranslatejoint.h @@ -0,0 +1,141 @@ +/** + * @file fsmaniptranslatejoint.h + * @brief custom manipulator for moving joints + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (c) 2025 Angeldark Raymaker @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#ifndef FS_MANIP_TRANSLATE_JOINT_H +#define FS_MANIP_TRANSLATE_JOINT_H + +#include "llselectmgr.h" +#include "fsmaniprotatejoint.h" +#include "llmaniptranslate.h" + +class LLJoint; +class LLVOAvatar; // for LLVOAvatarSelf, etc. + +class FSManipTranslateJoint : public LLManipTranslate +{ +public: + class ManipulatorHandle + { + public: + LLVector3 mStartPosition; + LLVector3 mEndPosition; + EManipPart mManipID; + F32 mHotSpotRadius; + + ManipulatorHandle(LLVector3 start_pos, LLVector3 end_pos, EManipPart id, F32 radius):mStartPosition(start_pos), mEndPosition(end_pos), mManipID(id), mHotSpotRadius(radius){} + }; + + + FSManipTranslateJoint(LLToolComposite* composite); + virtual ~FSManipTranslateJoint() {}; + + /// + /// Sets the joint we are going to manipulate. + /// + /// The joint to interact with. + void setJoint(LLJoint* joint); + + /// + /// Sets the avatar the manip should interact with. + /// + /// The avatar to interact with. + void setAvatar(LLVOAvatar* avatar); + + /// + /// Sets the avatar the manip should interact with. + /// + /// The avatar to interact with. + void setReferenceFrame(const E_PoserReferenceFrame frame) { mReferenceFrame = frame; }; + + static U32 getGridTexName() ; + static void destroyGL(); + static void restoreGL(); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual void render(); + virtual void handleSelect(); + + virtual void highlightManipulators(S32 x, S32 y); + virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask); + virtual bool canAffectSelection(); + +protected: + enum EHandleType { + HANDLE_CONE, + HANDLE_BOX, + HANDLE_SPHERE + }; + + void renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction); + void renderTranslationHandles(); + void renderText(); + +private: + bool isAvatarJointSafeToUse(); + + /// + /// Determines if the currently selected joint is one to move with manip. + /// + /// True if we can move the joint with manip, otherwise false. + bool isMoveableJoint(); + void getManipJointNormal(LLJoint* joint, EManipPart manip, LLVector3& normal) { getManipNormal(nullptr, manip, normal);}; + bool getManipJointAxis(LLJoint* joint, EManipPart manip, LLVector3& axis) { return getManipAxis(nullptr, manip, axis); }; + + LLVector3 getChangeInPosition(LLVector3 newPosition) const; + + S32 mLastHoverMouseX; + S32 mLastHoverMouseY; + bool mMouseOutsideSlop; // true after mouse goes outside slop region + bool mCopyMadeThisDrag; + S32 mMouseDownX; + S32 mMouseDownY; + F32 mAxisArrowLength; // pixels + F32 mConeSize; // meters, world space + F32 mArrowLengthMeters; // meters + F32 mPlaneManipOffsetMeters; + LLVector3 mManipNormal; + LLVector3 mDragCursorLastGlobal; + LLVector3d mDragCursorStartGlobal; + LLVector3d mDragSelectionStartGlobal; + LLTimer mUpdateTimer; + LLVector4 mManipulatorVertices[18]; + F32 mSnapOffsetMeters; + LLVector3 mSnapOffsetAxis; + LLQuaternion mGridRotation; + LLVector3 mGridOrigin; + LLVector3 mGridScale; + LLVector3 mArrowScales; + LLVector3 mPlaneScales; + LLVector4 mPlaneManipPositions; + + LLJoint* mJoint = nullptr; + LLVOAvatar* mAvatar = nullptr; + E_PoserReferenceFrame mReferenceFrame = POSER_FRAME_BONE; +}; + +#endif diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index 31b35fa8f1..a62bc78530 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -114,9 +114,26 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint if (!jointPose) return; - if (jointPose->undoLastChange()) + E_PoserChangeType changeType = jointPose->undoLastChange(); + if (changeType == POSER_CHANGE_ROTATION) undoOrRedoWorldLockedDescendants(joint, posingMotion, false); + if (changeType == POSER_CHANGE_CHILDMOVE) + { + undoOrRedoRotatedParents(joint, posingMotion, false); + + FSJointPose* parentJointPose = findParentJointPose(posingMotion, joint.jointName()); + if (parentJointPose) + { + FSJointPose* grandParentJointPose = findParentJointPose(posingMotion, parentJointPose->jointName()); + if (grandParentJointPose) + { + auto grandParentPoserJoint = getPoserJointByName(grandParentJointPose->jointName()); + undoOrRedoWorldLockedDescendants(*grandParentPoserJoint, posingMotion, false); + } + } + } + if (style == NONE || style == DELTAMODE) return; @@ -124,7 +141,8 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint if (!oppositeJointPose) return; - if (!oppositeJointPose->undoLastChange()) + changeType = oppositeJointPose->undoLastChange(); + if (changeType != POSER_CHANGE_ROTATION) return; auto oppositePoserJoint = getPoserJointByName(joint.mirrorJointName()); @@ -198,8 +216,10 @@ void FSPoserAnimator::redoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint if (!jointPose) return; - jointPose->redoLastChange(); + E_PoserChangeType changeType = jointPose->redoLastChange(); undoOrRedoWorldLockedDescendants(joint, posingMotion, true); + if (changeType == POSER_CHANGE_CHILDMOVE) + undoOrRedoRotatedParents(joint, posingMotion, true); if (style == NONE || style == DELTAMODE) return; @@ -465,6 +485,15 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi 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 (!position.isExactlyZero()) + updateJointPositionFromManip(avatar, joint, resetBaseRotationToZero, style, position); + else + updateJointRotationFromManip(avatar, joint, resetBaseRotationToZero, style, frame, rotation); +} + +void FSPoserAnimator::updateJointRotationFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, + E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion& rotation) { if (!isAvatarSafeToUse(avatar)) return; @@ -478,7 +507,7 @@ void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoin return; LLQuaternion framedRotation = changeToRotationFrame(avatar, rotation, frame, jointPose); - jointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * jointPose->getPublicRotation()); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, framedRotation * jointPose->getPublicRotation()); deRotateWorldLockedDescendants(joint, posingMotion, framedRotation); @@ -495,14 +524,16 @@ void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoin { case SYMPATHETIC: case SYMPATHETIC_DELTA: - oppositeJointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * oppositeJointPose->getPublicRotation()); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, + framedRotation * oppositeJointPose->getPublicRotation()); if (oppositePoserJoint) deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, framedRotation); break; case MIRROR: case MIRROR_DELTA: - oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation()); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, + mirroredRotation * oppositeJointPose->getPublicRotation()); if (oppositePoserJoint) deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation); break; @@ -512,6 +543,207 @@ void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoin } } +void FSPoserAnimator::updateJointPositionFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, + E_BoneDeflectionStyles style, const LLVector3& position) +{ + if (!isAvatarSafeToUse(avatar)) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + LLJoint* movedJoint = avatar->getJoint(joint->jointName()); + if (!movedJoint) + return; + + LLJoint* parentJoint = movedJoint->getParent(); + if (!parentJoint) + return; + + if (jointIsPelvis(movedJoint)) + { + auto movedPoserJoint = getPoserJointByName(joint->jointName()); + LLVector3 currentPosition = getJointPosition(avatar, *movedPoserJoint); + LLQuaternion rootWorldRot = parentJoint->getWorldRotation(); + currentPosition += position * ~rootWorldRot; + + setJointPosition(avatar, movedPoserJoint, currentPosition, POSER_FRAME_WORLD, NONE); + + return; + } + + if (jointIsPelvis(parentJoint)) + return; + + LLJoint* grandParentJoint = parentJoint->getParent(); + if (!grandParentJoint || jointIsPelvis(grandParentJoint)) + return; + + LLVector3 oldMovedJointPosition = movedJoint->getWorldPosition(); + LLVector3 parentPosition = parentJoint->getWorldPosition(); + LLVector3 grandParentPosition = grandParentJoint->getWorldPosition(); + LLVector3 newMovedJointPosition = oldMovedJointPosition + position; + LLQuaternion grandParentWorldRot = grandParentJoint->getWorldRotation(); + LLQuaternion parentWorldRot = parentJoint->getWorldRotation(); + + FSJointPose* grandParentJointPose = posingMotion->getJointPoseByJointName(grandParentJoint->getName()); + FSJointPose* parentJointPose = posingMotion->getJointPoseByJointName(parentJoint->getName()); + FSJointPose* movedJointPose = posingMotion->getJointPoseByJointName(movedJoint->getName()); + if (!grandParentJointPose || !parentJointPose) + return; + + LLQuaternion grandParentFrameRotChange, grandParentLengthRotChange, parentLengthRotChange; + if (parentJointIsHinge(parentJoint)) + { + if (!canMoveToNewPosition(grandParentPosition, parentPosition, oldMovedJointPosition, newMovedJointPosition)) + return; + + grandParentFrameRotChange = getAbcFrameRotation(grandParentPosition, parentPosition, oldMovedJointPosition, position); + + if (!getPlanarRotationAtAandB(grandParentPosition, parentPosition, oldMovedJointPosition, position, grandParentLengthRotChange, + parentLengthRotChange)) + return; + } + else + { + grandParentFrameRotChange.loadIdentity(); // unused here + if (!getFreeRotationAtAandB(grandParentPosition, parentPosition, oldMovedJointPosition, position, grandParentLengthRotChange, + parentLengthRotChange)) + return; + } + + // convert rotations to local frame + grandParentFrameRotChange = grandParentWorldRot * grandParentFrameRotChange * ~grandParentWorldRot; + grandParentLengthRotChange = grandParentWorldRot * grandParentLengthRotChange * ~grandParentWorldRot; + parentLengthRotChange = parentWorldRot * parentLengthRotChange * ~parentWorldRot; + + movedJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_CHILDMOVE, movedJointPose->getPublicRotation()); // set up undo + parentJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, + parentLengthRotChange * parentJointPose->getPublicRotation()); + grandParentJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, + grandParentLengthRotChange * grandParentJointPose->getPublicRotation()); + grandParentJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, + grandParentFrameRotChange * grandParentJointPose->getPublicRotation()); + + auto grandParentPoserJoint = getPoserJointByName(grandParentJoint->getName()); + deRotateWorldLockedDescendants(grandParentPoserJoint, posingMotion, grandParentLengthRotChange); +} + +bool FSPoserAnimator::getFreeRotationAtAandB(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 changeAtC, + LLQuaternion& changeAtA, LLQuaternion& changeAtB) +{ + LLVector3 vectorAC = posC - posA; + LLVector3 vectorAB = posB - posA; + F32 lengthAC = vectorAC.length(); + F32 lengthAB = vectorAB.length(); + F32 lengthBC = (posC - posB).length(); + F32 lengthMove = changeAtC.length(); + + LLVector3 rotationAxis = vectorAC % changeAtC; + rotationAxis.normalize(); + + F32 angle = asin(lengthMove / lengthAC); + if (isnan(angle)) + return false; + + changeAtA = LLQuaternion(angle, rotationAxis); + changeAtB = LLQuaternion(-angle, rotationAxis); + + return true; +} + +bool FSPoserAnimator::getPlanarRotationAtAandB(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 changeAtC, + LLQuaternion& changeAtA, LLQuaternion& changeAtB) +{ + LLVector3 vectorAC = posC - posA; + LLVector3 vectorAB = posB - posA; + F32 lengthAC = vectorAC.length(); + F32 lengthAB = vectorAB.length(); + F32 lengthBC = (posC - posB).length(); + + F32 lengthACbeforeRot = vectorAC.length(); + F32 lengthACafterRot = (posC + changeAtC - posA).length(); + LLVector3 rotationAxis = vectorAB % vectorAC; + + rotationAxis.normalize(); + + // Distance from posA, along AC, to where B is perpendicular to AC (right angle triangle), before and after moving C; intersecting spheres + F32 dist1 = (lengthAB * lengthAB - lengthBC * lengthBC + lengthACbeforeRot * lengthACbeforeRot) / (2.f * lengthACbeforeRot); + F32 dist2 = (lengthAB * lengthAB - lengthBC * lengthBC + lengthACafterRot * lengthACafterRot) / (2.f * lengthACafterRot); + + if (dist1 == dist2) + return false; + + F32 angle1 = acos(dist1 / lengthAB); + F32 angle2 = acos(dist2 / lengthAB); + changeAtA = LLQuaternion((angle1 - angle2), rotationAxis); + + angle1 = acos((lengthACbeforeRot - dist1) / lengthBC); + angle2 = acos((lengthACafterRot - dist2) / lengthBC); + changeAtB = LLQuaternion((angle2 - angle1), rotationAxis); + + return true; +} + +LLQuaternion FSPoserAnimator::getAbcFrameRotation(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 changeAtC) +{ + LLVector3 vectorAC = posC - posA; + LLVector3 vectorAnewC = vectorAC + changeAtC; + + F32 angle = acos((vectorAC * vectorAnewC) / (vectorAC.length() * vectorAnewC.length())); + LLVector3 rotationAxis = vectorAC % changeAtC; + rotationAxis.normalize(); + + if (isnan(angle)) + angle = 0.f; + + return LLQuaternion(angle, rotationAxis); +} + +bool FSPoserAnimator::parentJointIsHinge(LLJoint* parentJoint) +{ + if (!parentJoint) + return false; + + std::string jointName = parentJoint->getName(); + if (jointName.find("Elbow") != std::string::npos) + return true; + if (jointName.find("Knee") != std::string::npos) + return true; + if (jointName.find("Hand") != std::string::npos && jointName.find("2") != std::string::npos) // eg: mHandIndex2Right, the second 'knuckle' + return true; + + return false; +} + +bool FSPoserAnimator::jointIsPelvis(const LLJoint* joint) +{ + if (!joint) + return false; + + std::string jointName = joint->getName(); + return jointName == "mPelvis" || jointName == "mRoot"; +} + +bool FSPoserAnimator::canMoveToNewPosition(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 posNewC) +{ + F32 lengthAC = (posNewC - posA).length(); + if (lengthAC < 0.04f) + return false; // don't allow C and A to converge + + F32 lengthAB = (posB - posA).length(); + F32 lengthBC = (posC - posB).length(); + + if (lengthAC > lengthAB + lengthBC - 0.002f) + return false; // if the limbs become fully extended then moving back is non-trivial because the 3 joints are not a triangle + + if (lengthAC < fabs(lengthAB - lengthBC)) + return false; // absurdity + + return true; +} + LLQuaternion FSPoserAnimator::getManipGimbalRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, E_PoserReferenceFrame frame) { LLQuaternion globalRot(-1.f, 0.f, 0.f, 0.f); @@ -548,16 +780,19 @@ LLQuaternion FSPoserAnimator::getManipGimbalRotation(LLVOAvatar* avatar, const F if (frame == POSER_FRAME_BONE) return llJoint->getWorldRotation(); + return getQuaternionFromWorldVector(llJoint->getWorldPosition() - gAgentCamera.getCameraPositionAgent()); +} + +LLQuaternion FSPoserAnimator::getQuaternionFromWorldVector(const LLVector3 worldVector) +{ 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); + LLVector3 up, normVector(worldVector); + normVector.normalize(); + left.setVec(skyward % normVector); + up.setVec(normVector % left); - return LLQuaternion(jointToCameraPosition, left, up); + return LLQuaternion(normVector, left, up); } LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const @@ -633,28 +868,28 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j case SYMPATHETIC: case MIRROR: if (rotationStyle == DELTAIC_ROT || translationRequiresDelta) - jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation()); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation()); else - jointPose->setPublicRotation(resetBaseRotationToZero, absRot); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, absRot); break; case SYMPATHETIC_DELTA: case MIRROR_DELTA: - jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation()); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation()); break; case DELTAMODE: - jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation()); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation()); deRotateWorldLockedDescendants(joint, posingMotion, deltaRot); return; case NONE: default: if (rotationStyle == DELTAIC_ROT || translationRequiresDelta) - jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation()); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation()); else - jointPose->setPublicRotation(resetBaseRotationToZero, absRot); + jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, absRot); deRotateWorldLockedDescendants(joint, posingMotion, deltaRot); return; @@ -677,7 +912,8 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j break; case SYMPATHETIC_DELTA: - oppositeJointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * oppositeJointPose->getPublicRotation()); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, + deltaRot * oppositeJointPose->getPublicRotation()); if (oppositePoserJoint) deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, deltaRot); break; @@ -689,7 +925,8 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j break; case MIRROR_DELTA: - oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation()); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, + mirroredRotation * oppositeJointPose->getPublicRotation()); if (oppositePoserJoint) deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation); break; @@ -1071,7 +1308,7 @@ void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint* jointPose->purgeUndoQueue(); LLQuaternion rot = translateRotationToQuaternion(avatar, jointPose, POSER_FRAME_BONE, SWAP_NOTHING, NEGATE_NOTHING, rotation); - jointPose->setPublicRotation(setBaseToZero, rot); + jointPose->setPublicRotation(setBaseToZero, POSER_CHANGE_DEFAULT, rot); } void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position) @@ -1332,10 +1569,10 @@ bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, S32 depth) const { - size_t numberOfBvhChildNodes = joint->bvhChildren().size(); - if (numberOfBvhChildNodes < 1) + if (joint->bvhChildren().empty()) return depth; + size_t numberOfBvhChildNodes = joint->bvhChildren().size(); depth++; for (size_t index = 0; index != numberOfBvhChildNodes; ++index) @@ -1352,10 +1589,10 @@ int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, S32 depth) co void FSPoserAnimator::deRotateWorldLockedDescendants(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion rotationChange) { - size_t numberOfBvhChildNodes = joint->bvhChildren().size(); - if (numberOfBvhChildNodes < 1) + if (joint->bvhChildren().empty()) return; + size_t numberOfBvhChildNodes = joint->bvhChildren().size(); FSJointPose* parentJoint = posingMotion->getJointPoseByJointName(joint->jointName()); if (!parentJoint) return; @@ -1385,14 +1622,14 @@ void FSPoserAnimator::deRotateJointOrFirstLockedChild(const FSPoserJoint* joint, LLQuaternion rotDiffInChildFrame = differenceInWorldRot * rotationChange * ~differenceInWorldRot; rotDiffInChildFrame.conjugate(); - jointPose->setPublicRotation(false, rotDiffInChildFrame * jointPose->getPublicRotation()); + jointPose->setPublicRotation(false, POSER_CHANGE_ROTATION, rotDiffInChildFrame * jointPose->getPublicRotation()); return; } - size_t numberOfBvhChildNodes = joint->bvhChildren().size(); - if (numberOfBvhChildNodes < 1) + if (joint->bvhChildren().empty()) return; + size_t numberOfBvhChildNodes = joint->bvhChildren().size(); for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { auto nextJoint = getPoserJointByName(joint->bvhChildren().at(index)); @@ -1403,12 +1640,54 @@ void FSPoserAnimator::deRotateJointOrFirstLockedChild(const FSPoserJoint* joint, } } -void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo) +void FSPoserAnimator::undoOrRedoRotatedParents(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo) { - size_t numberOfBvhChildNodes = joint.bvhChildren().size(); - if (numberOfBvhChildNodes < 1) + if (!posingMotion) return; + FSJointPose* parentJointPose = findParentJointPose(posingMotion, joint.jointName()); + if (!parentJointPose) + return; + + if (redo) + parentJointPose->redoLastChange(); + else + parentJointPose->undoLastChange(); + + FSJointPose* grandParentJointPose = findParentJointPose(posingMotion, parentJointPose->jointName()); + if (!grandParentJointPose) + return; + + if (redo) + grandParentJointPose->redoLastChange(); + else + grandParentJointPose->undoLastChange(); +} + +FSJointPose* FSPoserAnimator::findParentJointPose(FSPosingMotion* posingMotion, std::string jointName) +{ + if (jointName.empty() || !posingMotion) + return nullptr; + + for (size_t index = 0; index != PoserJoints.size(); ++index) + { + auto children = PoserJoints[index].bvhChildren(); + auto it = std::find(children.begin(), children.end(), jointName); + if (it == children.end()) + continue; + + return posingMotion->getJointPoseByJointName(PoserJoints[index].jointName()); + } + + return nullptr; +} + +void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo) +{ + if (joint.bvhChildren().empty()) + return; + + size_t numberOfBvhChildNodes = joint.bvhChildren().size(); for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { auto nextJoint = getPoserJointByName(joint.bvhChildren().at(index)); @@ -1438,10 +1717,10 @@ void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& join return; } - size_t numberOfBvhChildNodes = joint.bvhChildren().size(); - if (numberOfBvhChildNodes < 1) + if (joint.bvhChildren().empty()) return; + size_t numberOfBvhChildNodes = joint.bvhChildren().size(); for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { auto nextJoint = getPoserJointByName(joint.bvhChildren().at(index)); diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 2f7cb32948..f0597a4068 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -792,19 +792,19 @@ public: void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion); private: - /// - /// 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. - /// - /// The avatar whose joint is being manipulated. - /// The joint which is being altered. - /// The frame of reference the translation should be performed in. - /// The axis translation to perform. - /// The style of axis-negation. - /// The rotation to translate and transform to quaternion. - /// The translated rotation quaternion. - LLQuaternion translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame, - E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation); + /// + /// 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. + /// + /// The avatar whose joint is being manipulated. + /// The joint which is being altered. + /// The frame of reference the translation should be performed in. + /// The axis translation to perform. + /// The style of axis-negation. + /// The rotation to translate and transform to quaternion. + /// The translated rotation quaternion. + LLQuaternion translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame, + E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation); /// /// Translates a bone-rotation quaternion to a vector usable easily on the UI. @@ -900,6 +900,88 @@ public: /// LLQuaternion changeToRotationFrame(LLVOAvatar* avatar, const LLQuaternion& rotation, E_PoserReferenceFrame frame, FSJointPose* joint); + void updateJointRotationFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, + E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion& rotation); + + void updateJointPositionFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, + E_BoneDeflectionStyles style, const LLVector3& position); + + /// + /// Undoes or re-does the parent and grandparent joint rotations from a updateJointPositionFromManip(...) + /// + /// The joint whose parent and grandparent need to undo. + /// The posing motion. + /// Whether to redo the rotation. + void undoOrRedoRotatedParents(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo); + + /// + /// Finds a jointPose using the supplied name. + /// + /// The posing motion. + /// The name of the joint. + /// The jointPose if found, otherwise nullptr. + FSJointPose* findParentJointPose(FSPosingMotion* posingMotion, std::string jointName); + + /// + /// Determines the rotation of the grand-parent joint to move a system containing a 'hinge' joint. + /// + /// The world position of the grand-parent joint. + /// The world position of the parent joint. + /// The world position of the moved joint. + /// The world-referenced movement of the moved joint. + /// The rotation required for the system of moved-joint, parent and grandparent. + /// + /// The first of a 2-part treatment to move a joint by changing the rotation of its two parents. + /// This method determines the rotation for the entire frame (a triangle) of joint-parent-grandparent. + /// It finds the vector of the grand-parent to child (vectorAC) before and after moment, then uses + /// dot-product to determine the angle change. + /// Cross product of the change-vector and AC determines the axis of rotation. + /// + LLQuaternion getAbcFrameRotation(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 changeAtC); + + /// + /// Determines the rotation changes for the grandparent and parent joints to position the child when the parent joint is a hinge. + /// + /// The world position of the grand-parent joint. + /// The world position of the parent joint. + /// The world position of the moved joint. + /// The world-referenced movement of the moved joint. + /// The rotation at the grandparent to extend or retract the moved joint. + /// The rotation at the parent to extend or retract the moved joint. + /// True if the rotation can be made, otherwise false. + /// + /// The second of a 2-part treatment to move a joint by changing the rotation of its two parents. + /// This method calculates rotations at grandparent and parent so that the length AC (grandparent to child) changes. + /// Both rotations are in the plane created by grandparent-parent-child. + /// This emulates the behaviour of a 'hinge' joint, like 'flexing' an elbow or knee. + /// Child movement is along vector AC (grandparent-child), and does not account for 'world movement' differences in C because of changeAtC, + /// as that is handled by getAbcFrameRotation. + /// + bool getPlanarRotationAtAandB(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 changeAtC, + LLQuaternion& changeAtA, LLQuaternion& changeAtB); + + /// + /// Determines the rotation changes for the grandparent and parent joints to position the child when all joints are 'socket' joints. + /// + /// The world position of the grand-parent joint. + /// The world position of the parent joint. + /// The world position of the moved joint. + /// The world-referenced movement of the moved joint. + /// The rotation at the grandparent to extend or retract the moved joint. + /// The rotation at the parent to extend or retract the moved joint. + /// True if the rotation can be made, otherwise false. + /// + /// Moves the child spherically in the specified direction by rotating the parent and grandparent. + /// + bool getFreeRotationAtAandB(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 changeAtC, + LLQuaternion& changeAtA, LLQuaternion& changeAtB); + + LLQuaternion getQuaternionFromWorldVector(const LLVector3 worldVector); + + bool parentJointIsHinge(LLJoint* parentJoint); + bool jointIsPelvis(const LLJoint* joint); + bool canMoveToNewPosition(const LLVector3 posA, const LLVector3 posB, const LLVector3 posC, const LLVector3 posNewC); + /// /// 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. diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index 91f018061d..45f179ce96 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -279,7 +279,7 @@ void FSPosingMotion::setAllRotationsToZeroAndClearUndo() for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) { poserJoint_iter->purgeUndoQueue(); - poserJoint_iter->setPublicRotation(true, LLQuaternion::DEFAULT); + poserJoint_iter->setPublicRotation(true, POSER_CHANGE_ROTATION, LLQuaternion::DEFAULT); } } diff --git a/indra/newview/lltoolcomp.cpp b/indra/newview/lltoolcomp.cpp index c4d3fbe167..63153125eb 100644 --- a/indra/newview/lltoolcomp.cpp +++ b/indra/newview/lltoolcomp.cpp @@ -1017,13 +1017,11 @@ bool FSToolCompPose::handleMouseUp(S32 x, S32 y, MASK mask) // If you want SHIFT+CTRL combos to do something else LLTool* FSToolCompPose::getOverrideTool(MASK mask) { - // Example from LLToolCompRotate that calls scale if SHIFT+CTRL - if (mask == (MASK_CONTROL | MASK_SHIFT)) + if (mask == MASK_CONTROL) { - // If you have a scale tool, return that. Or else remove - // this if you don't want an override. - return LLToolCompScale::getInstance(); + return FSToolCompPoseTranslate::getInstance(); } + // Otherwise fallback return LLToolComposite::getOverrideTool(mask); } @@ -1061,3 +1059,148 @@ void FSToolCompPose::render() LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); } } + +FSToolCompPoseTranslate* FSToolCompPoseTranslate::getInstance() +{ + // Meyers singleton pattern + static FSToolCompPoseTranslate instance; + return &instance; +} + +//----------------------------------- +// Constructor +FSToolCompPoseTranslate::FSToolCompPoseTranslate() : LLToolComposite(std::string("PoseTranslate")) +{ + // Create a joint manipulator + mManip = new FSManipTranslateJoint(this); + + // Set the default and current subtool + mCur = mManip; + mDefault = mManip; +} + +//----------------------------------- +// Destructor +FSToolCompPoseTranslate::~FSToolCompPoseTranslate() +{ + delete mManip; + mManip = nullptr; + + delete mSelectRect; + mSelectRect = nullptr; +} + +//----------------------------------- +// Handle Hover +bool FSToolCompPoseTranslate::handleHover(S32 x, S32 y, MASK mask) +{ + // If the current subtool hasn't captured the mouse, + // switch to your manip subtool (like LLToolCompRotate). + if (!mCur->hasMouseCapture()) + { + setCurrentTool(mManip); + } + return mCur->handleHover(x, y, mask); +} + +//----------------------------------- +// Handle MouseDown +bool FSToolCompPoseTranslate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDown = true; + + // Kick off an async pick, which calls pickCallback when complete + // so we can see if user clicked on a manip ring or not + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +//----------------------------------- +// The pickCallback +void FSToolCompPoseTranslate::pickCallback(const LLPickInfo& pick_info) +{ + FSToolCompPoseTranslate* self = FSToolCompPoseTranslate::getInstance(); + FSManipTranslateJoint* manip = self->mManip; + + if (!manip) return; // No manipulator available, exit + + // Highlight the manipulator based on the mouse position + manip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); + + if (!self->mMouseDown) + { + // No action needed if mouse is up; interaction is handled by highlight logic + return; + } + + // Check if a manipulator ring is highlighted + if (manip->getHighlightedPart() != LLManip::LL_NO_PART) + { + // Switch to the manipulator tool for dragging + self->setCurrentTool(manip); + manip->handleMouseDownOnPart(pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask); + } + else + { + // If no ring is highlighted, reset interaction or do nothing + LL_DEBUGS("FSToolCompPose") << "No manipulator ring selected" << LL_ENDL; + } +} + + +//----------------------------------- +// Handle MouseUp +bool FSToolCompPoseTranslate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mMouseDown = false; + // The base LLToolComposite sets mCur->handleMouseUp(...) + // and does other management + return LLToolComposite::handleMouseUp(x, y, mask); +} + +//----------------------------------- +// getOverrideTool +// If you want SHIFT+CTRL combos to do something else +LLTool* FSToolCompPoseTranslate::getOverrideTool(MASK mask) +{ + if (mask == (MASK_CONTROL | MASK_SHIFT)) + { + return LLToolCompScale::getInstance(); + } + // Otherwise fallback + return LLToolComposite::getOverrideTool(mask); +} + +//----------------------------------- +// Handle DoubleClick +bool FSToolCompPoseTranslate::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!mManip->getSelection()->isEmpty() && + mManip->getHighlightedPart() == LLManip::LL_NO_PART) + { + // Possibly show some pose properties or open the pose floater + mPoser = dynamic_cast(LLFloaterReg::showInstance("fs_poser")); + return true; + } + else + { + // If nothing selected, try a mouse down again + return handleMouseDown(x, y, mask); + } +} + +//----------------------------------- +// render +void FSToolCompPoseTranslate::render() +{ + // Render the current subtool + mCur->render(); + + // If the current subtool is not the manip, we can still + // optionally draw manip guidelines in the background + if (mCur != mManip) + { + mManip->renderGuidelines(); // or something similar if your manip has it + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + } +} diff --git a/indra/newview/lltoolcomp.h b/indra/newview/lltoolcomp.h index 04364b45e4..09d4fdecae 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -254,6 +254,7 @@ protected: // Subclass of LLToolComposite #include "fsmaniprotatejoint.h" // For FSManipRotateJoint +#include "fsmaniptranslatejoint.h" #include "fsfloaterposer.h" class FSToolCompPose : public LLToolComposite { @@ -292,5 +293,40 @@ protected: bool mMouseDown = false; }; +class FSToolCompPoseTranslate : public LLToolComposite +{ +public: + // Typical pattern: pass a name like "Pose" + FSToolCompPoseTranslate(); + virtual ~FSToolCompPoseTranslate(); + + // For some viewer patterns, we create a static singleton: + static FSToolCompPoseTranslate* getInstance(); + + // Overriding base events: + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual void render() override; + void setAvatar(LLVOAvatar* avatar) { mManip->setAvatar(avatar); }; + void setJoint(LLJoint* joint) { mManip->setJoint(joint); }; + void setReferenceFrame(E_PoserReferenceFrame frame) { mManip->setReferenceFrame(frame); }; + + // Optional override if you have SHIFT/CTRL combos + virtual LLTool* getOverrideTool(MASK mask) override; + + // The pick callback invoked on async pick + static void pickCallback(const LLPickInfo& pick_info); + void setPoserFloater(FSFloaterPoser* poser){ mPoser = poser; }; + FSFloaterPoser* getPoserFloater(){ return mPoser; }; +protected: + // Tools within this composite + FSManipTranslateJoint* mManip = nullptr; + LLToolSelectRect* mSelectRect = nullptr; + FSFloaterPoser* mPoser = nullptr; + bool mMouseDown = false; +}; + #endif // LL_TOOLCOMP_H diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index 25f211c2ac..a962430503 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -125,6 +125,7 @@ void LLToolMgr::initTools() gBasicToolset->addTool( LLToolCompInspect::getInstance() ); gFaceEditToolset->addTool( LLToolCamera::getInstance() ); gPoserToolset->addTool( FSToolCompPose::getInstance() ); + gPoserToolset->addTool( FSToolCompPoseTranslate::getInstance() ); // On startup, use "select" tool setCurrentToolset(gBasicToolset); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 2ea00e835b..7143c6736d 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -5105,9 +5105,12 @@ void LLViewerWindow::renderSelections( bool for_gl_pick, bool pick_parcel_walls, } // render the poser manipulator guides // if we have something selected those toosl should override - if ( (!for_hud) && (selection->isEmpty()) && (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPose::getInstance()) ) + if (!for_hud && selection->isEmpty()) { - FSToolCompPose::getInstance()->render(); + if (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPose::getInstance()) + FSToolCompPose::getInstance()->render(); + if (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPoseTranslate::getInstance()) + FSToolCompPoseTranslate::getInstance()->render(); } //