/** * @file fsmaniprotatejoint.h * @brief custom manipulator for rotating joints * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Phoenix Firestorm Viewer Source Code * Copyright (c) 2025 Beq Janus @ Second Life * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA * http://www.firestormviewer.org * $/LicenseInfo$ */ #ifndef FS_MANIP_ROTATE_JOINT_H #define FS_MANIP_ROTATE_JOINT_H #include "llselectmgr.h" #include "llmaniprotate.h" class LLJoint; class LLVOAvatar; // for LLVOAvatarSelf, etc. /// /// A set of reference frames for presenting the gimbal within. /// 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 S32 CIRCLE_STEPS = 100; constexpr F32 CIRCLE_STEP_SIZE = 2.0f * F_PI / CIRCLE_STEPS; constexpr F32 SQ_RADIUS = RADIUS_PIXELS * RADIUS_PIXELS; constexpr F32 WIDTH_PIXELS = 8; constexpr F32 MAX_MANIP_SELECT_DISTANCE = 100.f; constexpr F32 SNAP_ANGLE_INCREMENT = 5.625f; constexpr F32 SNAP_ANGLE_DETENTE = SNAP_ANGLE_INCREMENT; constexpr F32 SNAP_GUIDE_RADIUS_1 = 2.8f; constexpr F32 SNAP_GUIDE_RADIUS_2 = 2.4f; constexpr F32 SNAP_GUIDE_RADIUS_3 = 2.2f; constexpr F32 SNAP_GUIDE_RADIUS_4 = 2.1f; constexpr F32 SNAP_GUIDE_RADIUS_5 = 2.05f; constexpr F32 SNAP_GUIDE_INNER_RADIUS = 2.f; constexpr F32 SELECTED_MANIPULATOR_SCALE = 1.05f; constexpr F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; constexpr S32 VERTICAL_OFFSET = 100; } class FSManipRotateJoint : public LLManipRotate { // Used for overriding the natural "up" direction of a joint. // if no override is set then the world up direction is used unless the joint is vertical in which case we pick an arbitrary normal static std::unordered_map sReferenceUpVectors; struct BoneAxes { LLVector3 naturalX; LLVector3 naturalY; LLVector3 naturalZ; }; LLQuaternion computeAlignmentQuat( const BoneAxes& boneAxes ) const; BoneAxes computeBoneAxes() const; public: FSManipRotateJoint(LLToolComposite* composite); virtual ~FSManipRotateJoint() {} static std::string getManipPartString(EManipPart part); /// /// 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 reference frame for the manipulator. /// /// The E_PoserReferenceFrame to use. void setReferenceFrame(const E_PoserReferenceFrame frame) { mReferenceFrame = frame; }; // Overrides void handleSelect() override; bool updateVisiblity() override; void render() override; void renderNameXYZ(const LLQuaternion& rot); bool handleMouseDown(S32 x, S32 y, MASK mask) override; bool handleMouseUp(S32 x, S32 y, MASK mask) override; bool handleHover(S32 x, S32 y, MASK mask) override; void drag(S32 x, S32 y) override; bool isAlwaysRendered() override { return true; } void highlightManipulators(S32 x, S32 y) override; bool handleMouseDownOnPart(S32 x, S32 y, MASK mask) override; void highlightHoverSpheres(S32 mouseX, S32 mouseY); protected: LLQuaternion dragUnconstrained( S32 x, S32 y ); LLQuaternion dragConstrained( S32 x, S32 y ); LLVector3 getConstraintAxis() const { return mConstraintAxis; }; LLVector3 setConstraintAxis(); // Instead of selecting an LLViewerObject, we have a single joint LLJoint* mJoint = nullptr; BoneAxes mBoneAxes; LLQuaternion mNaturalAlignmentQuat; LLVOAvatar* mAvatar = nullptr; // We'll store the joint's original rotation for reference LLQuaternion mSavedJointRot; LLJoint * mHighlightedJoint = nullptr; F32 mHighlightedPartDistance = 0.f; LLVector3 mLastEuler = LLVector3::zero; // last euler angles in degrees LLVector3 mInitialIntersection; // The initial point on the manipulator’s sphere (in agent space) const std::vector getSelectableJoints(){ return sSelectableJoints; }; private: bool isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera, F32& outDistanceFromCenter) const; static const std::vector sSelectableJoints; // Structure holding parameters needed to render one manipulator ring. struct RingRenderParams { EManipPart part; // e.g. LL_ROT_Z, LL_ROT_Y, LL_ROT_X, etc. LLVector4 targetScale; // Target scale for mManipulatorScales for this part. float extraRotateAngle; // Extra rotation angle (in degrees) to apply. LLVector3 extraRotateAxis; // Axis for the extra rotation. LLColor4 primaryColor; // Primary ring color. LLColor4 secondaryColor; // Secondary ring color. int scaleIndex; // Which component of mManipulatorScales to use (0: X, 1: Y, 2: Z, 3: Roll). }; static const std::unordered_map sRingParams; void updateManipulatorScale(EManipPart part, LLVector4& scales); void renderActiveRing( F32 radius, F32 width, const LLColor4& front_color, const LLColor4& back_color); void renderManipulatorRings(const LLVector3& center, const LLQuaternion& finalAlignment); void renderCenterCircle(const F32 radius, const LLColor4& normal_color = LLColor4(0.7f,0.7f,0.7f,0.2f), const LLColor4& highlight_color = LLColor4(0.8f,0.8f,0.8f,0.3f)); // add missing f for float void renderCenterSphere(const F32 radius, const LLColor4& normal_color = LLColor4(0.7f,0.7f,0.7f,0.2f), const LLColor4& highlight_color = LLColor4(0.8f,0.8f,0.8f,0.3f)); // add missing f for float 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