diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3c9a572374..682c5f0b38 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -86,7 +86,7 @@ jobs: variants: ${{ matrix.configuration }} steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} @@ -95,14 +95,14 @@ jobs: with: python-version: "3.11" - name: Checkout build variables - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: secondlife/build-variables ref: master path: .build-variables - name: Checkout master-message-template - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: secondlife/master-message-template path: .master-message-template diff --git a/.github/workflows/build_sl_mac_only.yml b/.github/workflows/build_sl_mac_only.yml index 9a625f3065..db78364432 100644 --- a/.github/workflows/build_sl_mac_only.yml +++ b/.github/workflows/build_sl_mac_only.yml @@ -16,8 +16,6 @@ on: - "*alpha" - "*nightly" - "*preview" - schedule: - - cron: '00 03 * * *' # Run every day at 3am UTC env: AUTOBUILD_VARIABLES_FILE: ${{github.workspace}}/build-variables/variables EXTRA_ARGS: -DUSE_FMODSTUDIO=ON -DUSE_KDU=ON --crashreporting @@ -58,7 +56,7 @@ jobs: echo "/usr/local/bin" >> $GITHUB_PATH echo "$(brew --prefix)/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Use apt-based Python when inside the Ubuntu 22.04 container - name: Install Python 3.11 (container case) if: matrix.container_image == 'ubuntu:22.04' @@ -185,12 +183,12 @@ jobs: shell: bash - name: Get the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Checkout build vars (after the main code) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: FirestormViewer/fs-build-variables path: build-variables @@ -521,7 +519,7 @@ jobs: if: always() steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | fsutils/download_list.py diff --git a/.github/workflows/build_viewer.yml b/.github/workflows/build_viewer.yml index 8fe74f33b9..d2581ca226 100644 --- a/.github/workflows/build_viewer.yml +++ b/.github/workflows/build_viewer.yml @@ -58,7 +58,7 @@ jobs: echo "/usr/local/bin" >> $GITHUB_PATH echo "$(brew --prefix)/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Use apt-based Python when inside the Ubuntu 22.04 container - name: Install Python 3.11 (container case) if: matrix.container_image == 'ubuntu:22.04' @@ -185,12 +185,12 @@ jobs: shell: bash - name: Get the code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Checkout build vars (after the main code) - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: FirestormViewer/fs-build-variables path: build-variables @@ -521,7 +521,7 @@ jobs: if: always() steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | fsutils/download_list.py diff --git a/.github/workflows/deploy_only.yml b/.github/workflows/deploy_only.yml index 8cd0e08e89..1e68a5b3a3 100644 --- a/.github/workflows/deploy_only.yml +++ b/.github/workflows/deploy_only.yml @@ -31,7 +31,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: sparse-checkout: | fsutils/download_list.py diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 8f942fa11b..93bcafdea8 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -11,7 +11,7 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: 3.x diff --git a/.github/workflows/tag-fs-build.yml b/.github/workflows/tag-fs-build.yml index 5192eacbc5..4b13f25ef4 100644 --- a/.github/workflows/tag-fs-build.yml +++ b/.github/workflows/tag-fs-build.yml @@ -50,7 +50,7 @@ jobs: steps: # Checkout the Repository - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # Necessary to fetch all history for tagging diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index 9dbbca976b..02a28b4317 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -278,3 +278,18 @@ void LLChatEntry::paste() } } // + +// Add menu items to copy and/or insert mention URIs into chat +void LLChatEntry::insertMentionAtCursor(const std::string& str) +{ + S32 cursor_from_end = getLength() - getCursorPos(); + + insertText(str); + + std::string new_text(wstring_to_utf8str(getConvertedText())); + clear(); + appendTextImpl(new_text, LLStyle::Params(), true); + + setCursorPos(getLength() - cursor_from_end); +} +// diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index b87b15956c..7cffe854bb 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -71,6 +71,9 @@ public: // Changed to public so we can update history when using modifier keys void updateHistory(); + // Add menu items to copy and/or insert mention URIs into chat + void insertMentionAtCursor(const std::string& str); + // Fix linefeed pasting /*virtual*/ void paste(); diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 8c9888c029..1810915e2f 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -1268,6 +1268,7 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) p.button_flash_count(LLUI::getInstance()->mSettingGroups["config"]->getS32("FlashCount")); p.button_flash_rate(LLUI::getInstance()->mSettingGroups["config"]->getF32("FlashPeriod")); // + p.handle_right_mouse = false; // Fix tab rearranging when active tab was right clicked before // *TODO : It seems wrong not to use p in both cases considering the way p is initialized if (mCustomIconCtrlUsed) diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 3038719f12..9e00a65138 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2482,6 +2482,11 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) enable_registrar.add("FS.EnableViewLog", std::bind(&FSRegistrarUtils::checkIsEnabled, gFSRegistrarUtils, target_id, EFSRegistrarFunctionActionType::FS_RGSTR_ACT_VIEW_TRANSCRIPT)); // + // Add menu items to copy and/or insert mention URIs into chat + registrar.add("Mention.CopyURI", boost::bind(&LLUrlAction::copyURLToClipboard, "secondlife:///app/agent/" + target_id_str + "/mention")); + registrar.add("Mention.Chat", boost::bind(&LLTextBase::insertMentionAtCursor, this, "secondlife:///app/agent/" + target_id_str + "/mention")); + // + // FIRE-30725 - Add more group functions to group URL context menu registrar.add("FS.JoinGroup", std::bind(&LLUrlAction::executeSLURL, "secondlife:///app/firestorm/" + target_id_str + "/groupjoin", true)); registrar.add("FS.LeaveGroup", std::bind(&LLUrlAction::executeSLURL, "secondlife:///app/firestorm/" + target_id_str + "/groupleave", true)); @@ -2561,6 +2566,14 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) } // + // Add menu items to copy and/or insert mention URIs into chat + if (!parent_floater || (parent_floater->getName() != "panel_im" && parent_floater->getName() != "nearby_chat")) + { + menu->getChild("MentionURISeparator")->setVisible(false); + menu->getChild("mention_in_chat")->setVisible(false); + } + // + menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } @@ -4627,3 +4640,10 @@ void LLTextBase::setWordWrap(bool wrap) { mWordWrap = wrap; } + +// Add menu items to copy and/or insert mention URIs into chat +// virtual +void LLTextBase::insertMentionAtCursor(const std::string& str) +{ +} +// diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index c00c2b947a..9b0bee6f07 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -731,6 +731,9 @@ protected: void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link = e_underline::UNDERLINE_ALWAYS); S32 normalizeUri(std::string& uri); + // Add menu items to copy and/or insert mention URIs into chat + virtual void insertMentionAtCursor(const std::string& str); + protected: // virtual std::string _getSearchText() const override diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index ec78501354..4f9a05a8b8 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -1268,9 +1268,11 @@ bool LLWindowMacOSX::setCursorPosition(const LLCoordWindow position) // trigger mouse move callback LLCoordGL gl_pos; convertCoords(position, &gl_pos); - float scale = getSystemUISize(); - gl_pos.mX *= scale; - gl_pos.mY *= scale; + // gl_pos is not meant to be scaled and breaks our pie menu and possibly other things + //float scale = getSystemUISize(); + //gl_pos.mX *= scale; + //gl_pos.mY *= scale; + // mCallbacks->handleMouseMove(this, gl_pos, (MASK)0); return result; diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 8924e922b6..4877a9ceba 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -1108,7 +1108,6 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat // Keyboard handling for Linux. #if LL_LINUX -#if LL_SDL2 uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); // this is actually the SDL event.key.keysym.sym; uint32_t native_virtual_key_win = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); @@ -1127,16 +1126,6 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat mCEFLib->nativeKeyboardEventSDL2(key_event, native_virtual_key_win, native_modifiers, keypad); } -#else - - uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); - uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); - uint32_t native_modifiers = (uint32_t)(native_key_data["cef_modifiers"].asInteger()); - if( native_scan_code == '\n' ) - native_scan_code = '\r'; - mCEFLib->nativeKeyboardEvent(key_event, native_scan_code, native_virtual_key, native_modifiers); - -#endif // LL_SDL2 #endif // LL_LINUX // }; @@ -1171,7 +1160,6 @@ void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD #endif #if LL_LINUX -# if LL_SDL2 uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); @@ -1179,7 +1167,6 @@ void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD mCEFLib->nativeKeyboardEvent(dullahan::KE_KEY_DOWN, native_scan_code, native_virtual_key, native_modifiers); -#endif // LL_SDL2 #endif // LL_LINUX }; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index e21d067c75..131c440a7e 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -133,6 +133,7 @@ set(viewer_SOURCE_FILES fsfloaterprotectedfolders.cpp fsfloaterradar.cpp fsfloatersearch.cpp + fsfloatersplashscreensettings.cpp fsfloaterstatistics.cpp fsfloaterstreamtitle.cpp fsfloaterteleporthistory.cpp @@ -974,6 +975,7 @@ set(viewer_HEADER_FILES fsfloaterprotectedfolders.h fsfloaterradar.h fsfloatersearch.h + fsfloatersplashscreensettings.h fsfloaterstatistics.h fsfloaterstreamtitle.h fsfloaterteleporthistory.h @@ -2514,6 +2516,7 @@ elseif (DARWIN) XCODE_ATTRIBUTE_OTHER_LDFLAGS[arch=x86_64] "$(inherited) -L${CMAKE_CURRENT_BINARY_DIR}/llphysicsextensions/$,$,${CMAKE_CFG_INTDIR}>/ -lllphysicsextensions -Xlinker -map -Xlinker ${CMAKE_CURRENT_BINARY_DIR}/${VIEWER_BINARY_NAME}.MAP" XCODE_ATTRIBUTE_OTHER_LDFLAGS[arch=arm64] "$(inherited) -L${CMAKE_BINARY_DIR}/llphysicsextensionsos/$,$,${CMAKE_CFG_INTDIR}>/ -lllphysicsextensionsos" ) + add_dependencies(${VIEWER_BINARY_NAME} llphysicsextensionsos) elseif(HAVOK_TPV) set_target_properties(${VIEWER_BINARY_NAME} PROPERTIES @@ -2524,6 +2527,7 @@ elseif (DARWIN) XCODE_ATTRIBUTE_OTHER_LDFLAGS[arch=x86_64] "$(inherited) -L${ARCH_PREBUILT_DIRS}/ -lllphysicsextensions_tpv" XCODE_ATTRIBUTE_OTHER_LDFLAGS[arch=arm64] "$(inherited) -L${CMAKE_BINARY_DIR}/llphysicsextensionsos/$,$,${CMAKE_CFG_INTDIR}>/ -lllphysicsextensionsos" ) + add_dependencies(${VIEWER_BINARY_NAME} llphysicsextensionsos) else() target_link_libraries(${VIEWER_BINARY_NAME} llphysicsextensionsos) endif() diff --git a/indra/newview/ao.cpp b/indra/newview/ao.cpp index f3b3e390f7..490fd1a12a 100644 --- a/indra/newview/ao.cpp +++ b/indra/newview/ao.cpp @@ -152,11 +152,12 @@ void FloaterAO::updateList() mCurrentBoldItem = nullptr; reloading(false); + static std::string no_sets_label = getString("ao_no_sets_loaded"); if (mSetList.empty()) { LL_DEBUGS("AOEngine") << "empty set list" << LL_ENDL; - mSetSelector->add(getString("ao_no_sets_loaded")); - mSetSelectorSmall->add(getString("ao_no_sets_loaded")); + mSetSelector->add(no_sets_label); + mSetSelectorSmall->add(no_sets_label); mSetSelector->selectNthItem(0); mSetSelectorSmall->selectNthItem(0); enableSetControls(false); @@ -164,13 +165,13 @@ void FloaterAO::updateList() } // make sure we have an animation set name to display - if (currentSetName.empty()) + if (currentSetName.empty() || currentSetName == no_sets_label) { // selected animation set was empty, get the currently active animation set from the engine currentSetName = AOEngine::instance().getCurrentSetName(); LL_DEBUGS("AOEngine") << "Current set name was empty, fetched name \"" << currentSetName << "\" from AOEngine" << LL_ENDL; - if(currentSetName.empty()) + if (currentSetName.empty()) { // selected animation set was empty, get the name of the first animation set in the list currentSetName = mSetList[0]->getName(); @@ -374,7 +375,7 @@ void FloaterAO::onSelectSet() // only update the interface when we actually selected a different set - FIRE-29542 if (mSelectedSet != set) { - mSelectedSet=set; + mSelectedSet = set; updateSetParameters(); updateAnimationList(); diff --git a/indra/newview/aoengine.cpp b/indra/newview/aoengine.cpp index 19b53d3ba1..74c523b96a 100644 --- a/indra/newview/aoengine.cpp +++ b/indra/newview/aoengine.cpp @@ -40,10 +40,11 @@ #include "llnotificationsutil.h" #include "llstring.h" #include "llviewercontrol.h" +#include "llxorcipher.h" -#define ROOT_AO_FOLDER "#AO" -#include +#define ROOT_AO_FOLDER "#AO" +static const LLUUID ENCRYPTION_MAGIC_ID("4b552ff5-fd63-408c-8288-cd09429852ba"); constexpr F32 INVENTORY_POLLING_INTERVAL = 5.0f; AOEngine::AOEngine() : @@ -68,6 +69,31 @@ AOEngine::AOEngine() : AOEngine::~AOEngine() { + LLSD currentState = LLSD::emptyMap(); + if (mCurrentSet) + { + currentState = LLSD().with("CurrentSet", mCurrentSet->getName()); + LLSD currentAnimations; + for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) + { + if (auto state = mCurrentSet->getState(index); state && !state->mAnimations.empty()) + { + LL_DEBUGS("AOEngine") << "Storing current animation for state " << index << ": Animation index " << state->mCurrentAnimation << LL_ENDL; + LLUUID shadow_id{ state->mAnimations[state->mCurrentAnimation].mAssetUUID }; + LLXORCipher cipher(ENCRYPTION_MAGIC_ID.mData, UUID_BYTES); + cipher.encrypt(shadow_id.mData, UUID_BYTES); + currentAnimations.insert(state->mName, shadow_id); + } + else + { + LL_DEBUGS("AOEngine") << "No state " << index << " or no animations defined for this state" << LL_ENDL; + } + } + currentState.insert("CurrentStateAnimations", currentAnimations); + } + LL_DEBUGS("AOEngine") << "Stored AO state: " << currentState << LL_ENDL; + gSavedPerAccountSettings.setLLSD("FSCurrentAOState", currentState); + clear(false); if (mRegionChangeConnection.connected()) @@ -458,11 +484,9 @@ void AOEngine::enable(bool enable) // stop all overriders, catch leftovers for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) { - state = mCurrentSet->getState(index); - if (state) + if (auto state = mCurrentSet->getState(index)) { - LLUUID animation = state->mCurrentAnimationID; - if (animation.notNull()) + if (LLUUID animation = state->mCurrentAnimationID; animation.notNull()) { LL_DEBUGS("AOEngine") << "Stopping leftover animation from state " << state->mName << LL_ENDL; gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); @@ -488,7 +512,7 @@ void AOEngine::enable(bool enable) void AOEngine::setStateCycleTimer(const AOSet::AOState* state) { - F32 timeout = (F32)state->mCycleTime; + F32 timeout = state->mCycleTime; LL_DEBUGS("AOEngine") << "Setting cycle timeout for state " << state->mName << " of " << timeout << LL_ENDL; if (timeout > 0.0f) { @@ -583,7 +607,7 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) // case, as it plays at the same time as other motions if (motion != ANIM_AGENT_TYPE) { - constexpr S32 cleanupStates[]= + constexpr S32 cleanupStates[] = { AOSet::Standing, AOSet::Walking, @@ -615,15 +639,14 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) while ((stateNum = cleanupStates[index]) != AOSet::AOSTATES_MAX) { // check if the next state is the one we are currently animating and skip that - AOSet::AOState* stateToCheck = mCurrentSet->getState(stateNum); - if (stateToCheck != state) + if (AOSet::AOState* stateToCheck = mCurrentSet->getState(stateNum); stateToCheck != state) { // check if there is an animation left over for that state if (!stateToCheck->mCurrentAnimationID.isNull()) { LL_WARNS() << "cleaning up animation in state " << stateToCheck->mName << LL_ENDL; - // stop the leftover animation locally and in the region for everyone + // stop the leftover animation locally and in the region for everyone gAgent.sendAnimationRequest(stateToCheck->mCurrentAnimationID, ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(stateToCheck->mCurrentAnimationID); @@ -702,7 +725,7 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) } state->mCurrentAnimationID = animation; - LL_DEBUGS("AOEngine") << "overriding " << gAnimLibrary.animationName(motion) + LL_DEBUGS("AOEngine") << "overriding " << gAnimLibrary.animationName(motion) << " with " << animation << " in state " << state->mName << " of set " << mCurrentSet->getName() @@ -803,22 +826,21 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) void AOEngine::checkSitCancel() { - if (foreignAnimations()) + if (!foreignAnimations()) + return; + + if (AOSet::AOState* aoState = mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT)) { - if (AOSet::AOState* aoState = mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT); aoState) + if (LLUUID animation = aoState->mCurrentAnimationID; animation.notNull()) { - LLUUID animation = aoState->mCurrentAnimationID; - if (animation.notNull()) - { - LL_DEBUGS("AOEngine") << "Stopping sit animation due to foreign animations running" << LL_ENDL; - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); - // remove cycle point cover-up - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(animation); - mSitCancelTimer.stop(); - // stop cycle tiemr - mCurrentSet->stopTimer(); - } + LL_DEBUGS("AOEngine") << "Stopping sit animation due to foreign animations running" << LL_ENDL; + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); + // remove cycle point cover-up + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(animation); + mSitCancelTimer.stop(); + // stop cycle tiemr + mCurrentSet->stopTimer(); } } } @@ -918,7 +940,7 @@ void AOEngine::cycle(eCycleMode cycleMode) { LL_DEBUGS("AOEngine") << "Asset UUID for cycled animation " << anim.mName << " not yet known, try to find it." << LL_ENDL; - if(LLViewerInventoryItem* item = gInventory.getItem(anim.mOriginalUUID) ; item) + if (LLViewerInventoryItem* item = gInventory.getItem(anim.mOriginalUUID); item) { LL_DEBUGS("AOEngine") << "Found asset UUID for cycled animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL; anim.mAssetUUID = item->getAssetUUID(); @@ -992,14 +1014,13 @@ void AOEngine::playAnimation(const LLUUID& animation) return; } - if (!state->mAnimations.size()) + if (state->mAnimations.empty()) { LL_DEBUGS("AOEngine") << "cycle without animations in state." << LL_ENDL; return; } LLViewerInventoryItem* item = gInventory.getItem(animation); - if (!item) { LL_WARNS("AOEngine") << "Inventory item for animation " << animation << " not found." << LL_ENDL; @@ -1007,10 +1028,10 @@ void AOEngine::playAnimation(const LLUUID& animation) } AOSet::AOAnimation anim; - anim.mName = item->LLInventoryItem::getName(); + anim.mName = item->getName(); anim.mInventoryUUID = item->getUUID(); anim.mOriginalUUID = item->getLinkedUUID(); - anim.mAssetUUID = LLUUID::null; + anim.mAssetUUID.setNull(); // if we can find the original animation already right here, save its asset ID, otherwise this will // be tried again in AOSet::getAnimationForState() and/or AOEngine::cycle() @@ -1100,7 +1121,7 @@ void AOEngine::updateSortOrder(AOSet::AOState* state) std::ostringstream numStr(""); numStr << index; - LL_DEBUGS("AOEngine") << "sort order is " << sortOrder << " but index is " << index + LL_DEBUGS("AOEngine") << "sort order is " << sortOrder << " but index is " << index << ", setting sort order description: " << numStr.str() << LL_ENDL; state->mAnimations[index].mSortOrder = index; @@ -1111,8 +1132,8 @@ void AOEngine::updateSortOrder(AOSet::AOState* state) LL_WARNS("AOEngine") << "NULL inventory item found while trying to copy " << state->mAnimations[index].mInventoryUUID << LL_ENDL; continue; } - LLPointer newItem = new LLViewerInventoryItem(item); + LLPointer newItem = new LLViewerInventoryItem(item); newItem->setDescription(numStr.str()); newItem->setComplete(true); newItem->updateServer(false); @@ -1181,7 +1202,7 @@ void AOEngine::addAnimation(const AOSet* set, AOSet::AOState* state, const LLInv bool success = createAnimationLink(state, item); gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); - if(success) + if (success) { if (reload) { @@ -1197,7 +1218,7 @@ void AOEngine::addAnimation(const AOSet* set, AOSet::AOState* state, const LLInv state->mAddQueue.push_back(item); // if this is the first queued animation for this state, create the folder asyncronously - if(state->mAddQueue.size() == 1) + if (state->mAddQueue.size() == 1) { gInventory.createNewCategory(set->getInventoryUUID(), LLFolderType::FT_NONE, state->mName, [this, state, reload, wasProtected](const LLUUID &new_cat_id) { @@ -1440,7 +1461,7 @@ bool AOEngine::swapWithNext(AOSet::AOState* state, S32 index) return true; } -void AOEngine::reloadStateAnimations(AOSet::AOState* state) +void AOEngine::reloadStateAnimations(AOSet* set, AOSet::AOState* state) { LLInventoryModel::item_array_t* items; LLInventoryModel::cat_array_t* dummy; @@ -1458,11 +1479,10 @@ void AOEngine::reloadStateAnimations(AOSet::AOState* state) << " asset " << item->getAssetUUID() << LL_ENDL; AOSet::AOAnimation anim; - anim.mName = item->LLInventoryItem::getName(); + anim.mName = item->getName(); anim.mInventoryUUID = item->getUUID(); anim.mOriginalUUID = item->getLinkedUUID(); - - anim.mAssetUUID = LLUUID::null; + anim.mAssetUUID.setNull(); // if we can find the original animation already right here, save its asset ID, otherwise this will // be tried again in AOSet::getAnimationForState() and/or AOEngine::cycle() @@ -1509,6 +1529,24 @@ void AOEngine::reloadStateAnimations(AOSet::AOState* state) } updateSortOrder(state); + + if (auto currentState = gSavedPerAccountSettings.getLLSD("FSCurrentAOState"); + currentState.has("CurrentSet") && currentState["CurrentSet"].asString() == set->getName() && + currentState.has("CurrentStateAnimations") && currentState["CurrentStateAnimations"].has(state->mName)) + { + auto currentStateAnimId{ currentState["CurrentStateAnimations"][state->mName].asUUID() }; + LLXORCipher cipher(ENCRYPTION_MAGIC_ID.mData, UUID_BYTES); + cipher.decrypt(currentStateAnimId.mData, UUID_BYTES); + + for (const auto& animation : state->mAnimations) + { + if (animation.mAssetUUID == currentStateAnimId) + { + state->mCurrentAnimation = animation.mSortOrder; + break; + } + } + } } void AOEngine::update() @@ -1559,19 +1597,27 @@ void AOEngine::update() continue; } - AOSet* newSet = getSetByName(params[0]); + auto setName{ params[0] }; + AOSet* newSet = getSetByName(setName); if (!newSet) { LL_DEBUGS("AOEngine") << "Adding set " << setFolderName << " to AO." << LL_ENDL; newSet = new AOSet(currentCategory->getUUID()); - newSet->setName(params[0]); + newSet->setName(setName); mSets.emplace_back(newSet); + + if (auto currentState = gSavedPerAccountSettings.getLLSD("FSCurrentAOState"); + currentState.has("CurrentSet") && currentState["CurrentSet"].asString() == setName) + { + LL_DEBUGS("AOEngine") << "Selecting current set from settings: " << setName << LL_ENDL; + mCurrentSet = newSet; + } } else { if (newSet->getComplete()) { - LL_DEBUGS("AOEngine") << "Set " << params[0] << " already complete. Skipping." << LL_ENDL; + LL_DEBUGS("AOEngine") << "Set " << setName << " already complete. Skipping." << LL_ENDL; continue; } LL_DEBUGS("AOEngine") << "Updating set " << setFolderName << " in AO." << LL_ENDL; @@ -1599,7 +1645,11 @@ void AOEngine::update() else if (params[num] == "**") { mDefaultSet = newSet; - mCurrentSet = newSet; + if (!mCurrentSet) + { + LL_DEBUGS("AOEngine") << "No set selected as current yet - setting default set as current: " << setName << LL_ENDL; + mCurrentSet = newSet; + } } else { @@ -1666,12 +1716,12 @@ void AOEngine::update() newSet->setComplete(false); continue; } - reloadStateAnimations(state); + reloadStateAnimations(newSet, state); } } else { - LL_DEBUGS("AOEngine") << "Set " << params[0] << " is incomplete, fetching descendents" << LL_ENDL; + LL_DEBUGS("AOEngine") << "Set " << setName << " is incomplete, fetching descendents" << LL_ENDL; gInventory.fetchDescendentsOf(currentCategory->getUUID()); } } @@ -1683,7 +1733,7 @@ void AOEngine::update() if (!mCurrentSet && !mSets.empty()) { - LL_DEBUGS("AOEngine") << "No default set defined, choosing the first in the list." << LL_ENDL; + LL_DEBUGS("AOEngine") << "No set currently selected, choosing the first in the list." << LL_ENDL; selectSet(mSets[0]); } @@ -1723,16 +1773,15 @@ void AOEngine::reload(bool aFromTimer) AOSet* AOEngine::getSetByName(const std::string& name) const { - AOSet* found = nullptr; for (auto set : mSets) { - if (set->getName().compare(name) == 0) + if (set->getName() == name) { - found = set; - break; + return set; } } - return found; + + return nullptr; } const std::string AOEngine::getCurrentSetName() const @@ -1753,8 +1802,7 @@ void AOEngine::selectSet(AOSet* set) { if (mEnabled && mCurrentSet) { - AOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastOverriddenMotion); - if (state) + if (AOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastOverriddenMotion)) { gAgent.sendAnimationRequest(state->mCurrentAnimationID, ANIM_REQUEST_STOP); state->mCurrentAnimationID.setNull(); @@ -1773,8 +1821,7 @@ void AOEngine::selectSet(AOSet* set) AOSet* AOEngine::selectSetByName(const std::string& name) { - AOSet* set = getSetByName(name); - if (set) + if (AOSet* set = getSetByName(name)) { selectSet(set); return set; @@ -2176,8 +2223,7 @@ void AOEngine::onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::ETyp char* buffer = new char[notecardSize + 1]; buffer[notecardSize] = 0; - bool ret = file.read((U8*)buffer, notecardSize); - if (ret) + if (file.read((U8*)buffer, notecardSize)) { AOEngine::instance().parseNotecard(buffer); } @@ -2478,6 +2524,11 @@ void AOEngine::onRegionChange() gAgent.sendAnimationRequest(mLastMotion, ANIM_REQUEST_START); } +void AOEngine::updatePersistedStateAnimations() +{ + +} + // ---------------------------------------------------- AOSitCancelTimer::AOSitCancelTimer() @@ -2487,10 +2538,6 @@ AOSitCancelTimer::AOSitCancelTimer() mEventTimer.stop(); } -AOSitCancelTimer::~AOSitCancelTimer() -{ -} - void AOSitCancelTimer::oneShot() { mTickCount = 0; @@ -2525,10 +2572,6 @@ AOTimerCollection::AOTimerCollection() updateTimers(); } -AOTimerCollection::~AOTimerCollection() -{ -} - bool AOTimerCollection::tick() { if (mInventoryTimer) diff --git a/indra/newview/aoengine.h b/indra/newview/aoengine.h index 438fe217b9..fc5b3f3bfd 100644 --- a/indra/newview/aoengine.h +++ b/indra/newview/aoengine.h @@ -39,7 +39,7 @@ class AOTimerCollection { public: AOTimerCollection(); - ~AOTimerCollection(); + ~AOTimerCollection() = default; virtual bool tick(); @@ -64,7 +64,7 @@ class AOSitCancelTimer { public: AOSitCancelTimer(); - ~AOSitCancelTimer(); + ~AOSitCancelTimer() = default; void oneShot(); void stop(); @@ -101,7 +101,7 @@ class AOEngine void tick(); void update(); void reload(bool); - void reloadStateAnimations(AOSet::AOState* state); + void reloadStateAnimations(AOSet* set, AOSet::AOState* state); void clear(bool from_timer); const LLUUID& getAOFolder() const; @@ -196,6 +196,8 @@ class AOEngine void* userdata, S32 status, LLExtStat extStatus); void parseNotecard(const char* buffer); + void updatePersistedStateAnimations(); + updated_signal_t mUpdatedSignal; animation_changed_signal_t mAnimationChangedSignal; diff --git a/indra/newview/aoset.cpp b/indra/newview/aoset.cpp index 8d5e8d17de..d44f9cd848 100644 --- a/indra/newview/aoset.cpp +++ b/indra/newview/aoset.cpp @@ -171,8 +171,7 @@ const LLUUID& AOSet::getAnimationForState(AOState* state) const { if (state) { - auto numOfAnimations = state->mAnimations.size(); - if (numOfAnimations) + if (auto numOfAnimations = state->mAnimations.size(); numOfAnimations > 0) { if (state->mCycle) { @@ -198,7 +197,7 @@ const LLUUID& AOSet::getAnimationForState(AOState* state) const { LL_DEBUGS("AOEngine") << "Asset UUID for chosen animation " << anim.mName << " not yet known, try to find it." << LL_ENDL; - if(LLViewerInventoryItem* item = gInventory.getItem(anim.mInventoryUUID) ; item) + if (LLViewerInventoryItem* item = gInventory.getItem(anim.mInventoryUUID)) { LL_DEBUGS("AOEngine") << "Found asset UUID for chosen animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL; anim.mAssetUUID = item->getAssetUUID(); @@ -256,7 +255,7 @@ const std::string& AOSet::getName() const void AOSet::setName(const std::string& name) { - mName=name; + mName = name; } bool AOSet::getSitOverride() const diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 066cc467ed..fcb09ca7c3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8177,6 +8177,17 @@ Value 0 + FSPoserShowBoneHighlights + + Comment + Whether to highlight a bone with the debug beacon on selection from the UI. + Persist + 1 + Type + Boolean + Value + 1 + FSPoserStopPosingWhenClosed Comment @@ -25399,6 +25410,94 @@ Change of this parameter will affect the layout of buttons in notification toast HideFromEditor 1 + FSSplashScreenHideTopBar + + Comment + Hide the top bar section on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenHideBlogs + + Comment + Hide the blogs section on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenHideDestinations + + Comment + Hide the destinations section on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenUseGrayMode + + Comment + Enable grayscale mode on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenUseHighContrast + + Comment + Enable high contrast mode on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenUseAllCaps + + Comment + Enable all caps mode on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenUseLargerFonts + + Comment + Use larger fonts on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + + FSSplashScreenNoTransparency + + Comment + Disable transparency effects on the splash screen + Persist + 1 + Type + Boolean + Value + 0 + FSllOwnerSayToScriptDebugWindowRouting Comment diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml index 1641ec6b89..c95f78f244 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -365,6 +365,19 @@ Value 1 + GlobalOnlineStatusCurrentlyReverting + + Comment + Flag to prevent infinite loop when reverting the setting on cancel + HideFromEditor + 1 + Persist + 0 + Type + Boolean + Value + 0 + InstantMessageLogPath Comment @@ -1504,5 +1517,16 @@ Value 1 + FSCurrentAOState + + Comment + The currently active AO set and animation per state + Persist + 1 + Type + LLSD + Value + + diff --git a/indra/newview/fschathistory.cpp b/indra/newview/fschathistory.cpp index 8284ace393..51df9bdd46 100644 --- a/indra/newview/fschathistory.cpp +++ b/indra/newview/fschathistory.cpp @@ -128,6 +128,8 @@ LLObjectIMHandler gObjectIMHandler; class FSChatHistoryHeader: public LLPanel { public: + typedef boost::function insert_mention_callback_t; + FSChatHistoryHeader() : LLPanel(), mInfoCtrl(NULL), @@ -150,6 +152,7 @@ public: mTimeBoxTextBox(NULL), mHeaderLayoutStack(NULL), mAvatarNameCacheConnection(), + mInsertMentionCallback(NULL), mTime(0) {} @@ -180,6 +183,24 @@ public: } } + void setInsertMentionCallback(insert_mention_callback_t cb) + { + mInsertMentionCallback = cb; + } + + void copyURLToClipboard() + { + LLUrlAction::copyURLToClipboard("secondlife:///app/agent/" + mAvatarID.asString() + "/mention"); + } + + void insertMentionAtCursor() + { + if (mInsertMentionCallback) + { + mInsertMentionCallback("secondlife:///app/agent/" + mAvatarID.asString() + "/mention"); + } + } + bool handleMouseUp(S32 x, S32 y, MASK mask) { return LLPanel::handleMouseUp(x,y,mask); @@ -1070,6 +1091,9 @@ protected: registrar_enable.add("AvatarIcon.Enable", boost::bind(&FSChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2)); registrar_enable.add("AvatarIcon.Visible", boost::bind(&FSChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2)); + registrar.add("Mention.CopyURI", boost::bind(&FSChatHistoryHeader::copyURLToClipboard, this)); + registrar.add("Mention.Chat", boost::bind(&FSChatHistoryHeader::insertMentionAtCursor, this)); + menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); if (menu) { @@ -1257,6 +1281,8 @@ protected: private: boost::signals2::connection mAvatarNameCacheConnection; + + insert_mention_callback_t mInsertMentionCallback; }; FSChatHistory::FSChatHistory(const FSChatHistory::Params& p) @@ -1359,6 +1385,17 @@ void FSChatHistory::initFromParams(const FSChatHistory::Params& p) setShowContextMenu(true); } +// virtual +void FSChatHistory::insertMentionAtCursor(const std::string& str) +{ + updateChatInputLine(); + if (mChatInputLine) + { + mChatInputLine->insertMentionAtCursor(str); + mChatInputLine->setFocus(true); + } +} + LLView* FSChatHistory::getSeparator() { LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance()); @@ -1371,6 +1408,7 @@ LLView* FSChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style if (header) { header->setup(chat, style_params, args); + header->setInsertMentionCallback(boost::bind(&FSChatHistory::insertMentionAtCursor, this, _1)); } return header; } diff --git a/indra/newview/fschathistory.h b/indra/newview/fschathistory.h index eea5f15067..a041d57ed0 100644 --- a/indra/newview/fschathistory.h +++ b/indra/newview/fschathistory.h @@ -110,6 +110,8 @@ class FSChatHistory : public LLTextEditor // FIRE-8600: TAB out of cha LLSD getValue() const; void initFromParams(const Params&); + virtual void insertMentionAtCursor(const std::string& str); + /** * Appends a widget message. * If last user appended message, concurs with current user, diff --git a/indra/newview/fscontactsfriendsmenu.cpp b/indra/newview/fscontactsfriendsmenu.cpp index 120d102af9..3573ac0fa0 100644 --- a/indra/newview/fscontactsfriendsmenu.cpp +++ b/indra/newview/fscontactsfriendsmenu.cpp @@ -61,6 +61,7 @@ LLContextMenu* FSContactsFriendsMenu::createMenu() registrar.add("Contacts.Friends.CopyLabel", boost::bind(&FSContactsFriendsMenu::copyNameToClipboard, this, id)); registrar.add("Contacts.Friends.CopyUrl", boost::bind(&FSContactsFriendsMenu::copySLURLToClipboard, this, id)); registrar.add("Contacts.Friends.SelectOption", boost::bind(&FSContactsFriendsMenu::selectOption, this, _2)); + registrar.add("Mention.CopyURI", boost::bind(&FSContactsFriendsMenu::copyURLToClipboard, this)); enable_registrar.add("Contacts.Friends.EnableItem", boost::bind(&FSContactsFriendsMenu::enableContextMenuItem, this, _2)); enable_registrar.add("Contacts.Friends.EnableZoomIn", boost::bind(&LLAvatarActions::canZoomIn, id)); @@ -237,3 +238,8 @@ bool FSContactsFriendsMenu::checkOption(const LLSD& userdata) return false; } + +void FSContactsFriendsMenu::copyURLToClipboard() +{ + LLUrlAction::copyURLToClipboard("secondlife:///app/agent/" + mUUIDs.front().asString() + "/mention"); +} diff --git a/indra/newview/fscontactsfriendsmenu.h b/indra/newview/fscontactsfriendsmenu.h index d8c0e21e6f..7c3dcff836 100644 --- a/indra/newview/fscontactsfriendsmenu.h +++ b/indra/newview/fscontactsfriendsmenu.h @@ -44,6 +44,7 @@ private: void copySLURLToClipboard(const LLUUID& id); void selectOption(const LLSD& userdata); bool checkOption(const LLSD& userdata); + void copyURLToClipboard(); }; extern FSContactsFriendsMenu gFSContactsFriendsMenu; diff --git a/indra/newview/fsfloatercontacts.cpp b/indra/newview/fsfloatercontacts.cpp index 03c3cd8e95..f976f0218b 100644 --- a/indra/newview/fsfloatercontacts.cpp +++ b/indra/newview/fsfloatercontacts.cpp @@ -271,7 +271,7 @@ void FSFloaterContacts::onOpen(const LLSD& key) LLFloater::onOpen(key); } -void FSFloaterContacts::openTab(const std::string& name) +void FSFloaterContacts::openTab(std::string_view name) { if (name == "friends") { @@ -291,8 +291,7 @@ void FSFloaterContacts::openTab(const std::string& name) return; } - FSFloaterIMContainer* floater_container = dynamic_cast(getHost()); - if (floater_container) + if (auto floater_container = dynamic_cast(getHost())) { floater_container->setVisible(true); floater_container->showFloater(this); @@ -391,7 +390,7 @@ void FSFloaterContacts::onAvatarPicked(const uuid_vec_t& ids, const std::vector< { if (!names.empty() && !ids.empty()) { - LLAvatarActions::requestFriendshipDialog(ids[0], names[0].getCompleteName()); + LLAvatarActions::requestFriendshipDialog(ids.front(), names.front().getCompleteName()); } } @@ -405,8 +404,7 @@ void FSFloaterContacts::onAddFriendWizButtonClicked(LLUICtrl* ctrl) picker->setOkBtnEnableCb(boost::bind(&FSFloaterContacts::isItemsFreeOfFriends, this, _1)); } - LLFloater* root_floater = gFloaterView->getParentFloater(this); - if (root_floater) + if (auto root_floater = gFloaterView->getParentFloater(this)) { root_floater->addDependentFloater(picker); } @@ -474,7 +472,7 @@ std::string FSFloaterContacts::getActiveTabName() const return mTabContainer->getCurrentPanel()->getName(); } -LLPanel* FSFloaterContacts::getPanelByName(const std::string& panel_name) +LLPanel* FSFloaterContacts::getPanelByName(std::string_view panel_name) { return mTabContainer->getPanelByName(panel_name); } @@ -518,10 +516,9 @@ void FSFloaterContacts::getCurrentItemIDs(uuid_vec_t& selected_uuids) const void FSFloaterContacts::getCurrentFriendItemIDs(uuid_vec_t& selected_uuids) const { - listitem_vec_t selected = mFriendsList->getAllSelected(); - for (listitem_vec_t::iterator itr = selected.begin(); itr != selected.end(); ++itr) + for (auto list_item : mFriendsList->getAllSelected()) { - selected_uuids.push_back((*itr)->getUUID()); + selected_uuids.push_back(list_item->getUUID()); } } @@ -786,7 +783,7 @@ void FSFloaterContacts::refreshRightsChangeList() bool can_offer_teleport = num_selected >= 1; bool selected_friends_online = true; - const LLRelationship* friend_status = NULL; + const LLRelationship* friend_status = nullptr; for (const auto& id : friends) { friend_status = LLAvatarTracker::instance().getBuddyInfo(id); @@ -1064,8 +1061,8 @@ void FSFloaterContacts::sendRightsGrant(rights_map_t& ids) // setup message header msg->newMessageFast(_PREHASH_GrantUserRights); msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUID(_PREHASH_AgentID, gAgent.getID()); - msg->addUUID(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); for (const auto& [id, rights] : ids) { @@ -1078,7 +1075,7 @@ void FSFloaterContacts::sendRightsGrant(rights_map_t& ids) gAgent.sendReliableMessage(); } -void FSFloaterContacts::childShowTab(const std::string& id, const std::string& tabname) +void FSFloaterContacts::childShowTab(std::string_view id, std::string_view tabname) { if (LLTabContainer* child = findChild(id)) { diff --git a/indra/newview/fsfloatercontacts.h b/indra/newview/fsfloatercontacts.h index 5c3061eff3..949769d0e2 100644 --- a/indra/newview/fsfloatercontacts.h +++ b/indra/newview/fsfloatercontacts.h @@ -63,8 +63,8 @@ public: static FSFloaterContacts* getInstance(); static FSFloaterContacts* findInstance(); - void openTab(const std::string& name); - LLPanel* getPanelByName(const std::string& panel_name); + void openTab(std::string_view name); + LLPanel* getPanelByName(std::string_view panel_name); void sortFriendList(); void onDisplayNameChanged(); @@ -189,7 +189,7 @@ private: std::string mFriendFilterSubString{ LLStringUtil::null }; std::string mFriendFilterSubStringOrig{ LLStringUtil::null }; - void childShowTab(const std::string& id, const std::string& tabname); + void childShowTab(std::string_view id, std::string_view tabname); void updateRlvRestrictions(ERlvBehaviour behavior); boost::signals2::connection mRlvBehaviorCallbackConnection{}; diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index cd9934dc41..a09faa3ed6 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -46,6 +46,8 @@ #include "llinventoryfunctions.h" #include "lltoolcomp.h" #include "llloadingindicator.h" +#include "llmutelist.h" +#include "llappviewer.h" namespace { @@ -57,6 +59,7 @@ constexpr std::string_view POSE_PRESETS_HANDS_SUBDIRECTORY = "hand_presets"; constexpr char XML_LIST_HEADER_STRING_PREFIX[] = "header_"; constexpr char XML_LIST_TITLE_STRING_PREFIX[] = "title_"; constexpr char XML_JOINT_TRANSFORM_STRING_PREFIX[] = "joint_transform_"; +constexpr char XML_JOINT_FRAME_TRANSFORM_PREFIX[] = "joint_frame_"; constexpr char XML_JOINT_DELTAROT_STRING_PREFIX[] = "joint_delta_rotate_"; constexpr char BVH_JOINT_TRANSFORM_STRING_PREFIX[] = "bvh_joint_transform_"; constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpadSensitivity"; @@ -64,6 +67,7 @@ constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPos constexpr std::string_view POSER_SAVEEXTERNALFORMAT_SAVE_KEY = "FSPoserSaveExternalFileAlso"; constexpr std::string_view POSER_SAVECONFIRMREQUIRED_SAVE_KEY = "FSPoserOnSaveConfirmOverwrite"; constexpr std::string_view POSER_UNLOCKPELVISINBVH_SAVE_KEY = "FSPoserPelvisUnlockedForBvhSave"; +constexpr std::string_view POSER_SHOWBONEHIGHLIGHTS_SAVE_KEY = "FSManipShowJointMarkers"; constexpr char ICON_SAVE_OK[] = "icon_rotation_is_own_work"; constexpr char ICON_SAVE_FAILED[] = "icon_save_failed_button"; @@ -82,6 +86,7 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) mCommitCallbackRegistrar.add("Poser.StartStopAnimating", [this](LLUICtrl*, const LLSD&) { onPoseStartStop(); }); mCommitCallbackRegistrar.add("Poser.ToggleLoadSavePanel", [this](LLUICtrl*, const LLSD&) { onToggleLoadSavePanel(); }); mCommitCallbackRegistrar.add("Poser.ToggleVisualManipulators", [this](LLUICtrl*, const LLSD&) { onToggleVisualManipulators(); }); + mCommitCallbackRegistrar.add("Poser.ToggleRotationFrame", [this](LLUICtrl* button, const LLSD&) { onToggleRotationFrameButton(button); }); mCommitCallbackRegistrar.add("Poser.UndoLastRotation", [this](LLUICtrl*, const LLSD&) { onUndoLastChange(); }); mCommitCallbackRegistrar.add("Poser.RedoLastRotation", [this](LLUICtrl*, const LLSD&) { onRedoLastChange(); }); @@ -205,9 +210,17 @@ bool FSFloaterPoser::postBuild() mRedoChangeBtn = getChild("button_redo_change"); mUndoChangeBtn = getChild("undo_change"); mSetToTposeButton = getChild("set_t_pose_button"); + mBtnJointReset = getChild("poser_joint_reset"); + + mBtnWorldFrame = getChild("poser_world_frame_toggle"); + mBtnAvatarFrame = getChild("poser_avatar_frame_toggle"); + mBtnScreenFrame = getChild("poser_screen_frame_toggle"); mJointsParentPnl = getChild("joints_parent_panel"); mTrackballPnl = getChild("trackball_panel"); + mPositionPnl = getChild("position_panel"); + mMoveTabPnl = getChild("move_tab_panel"); + mTrackballButtonPnl = getChild("trackball_button_panel"); mPositionRotationPnl = getChild("positionRotation_panel"); mBodyJointsPnl = getChild("body_joints_panel"); mFaceJointsPnl = getChild("face_joints_panel"); @@ -269,6 +282,76 @@ void FSFloaterPoser::onFocusLost() } } +void FSFloaterPoser::draw() +{ + LLFloater::draw(); + + drawOnHoverJointHint(); +} + +void FSFloaterPoser::markSelectedJointsToHighlight() +{ + bool toolsEnabled = mToggleVisualManipulators->getValue().asBoolean(); + if (toolsEnabled) + return; + + bool showHighlights = gSavedSettings.getBOOL(POSER_SHOWBONEHIGHLIGHTS_SAVE_KEY); + if (!showHighlights) + return; + + auto selectedJoints = getUiSelectedPoserJoints(); + if (selectedJoints.empty()) + return; + + std::string jointName = selectedJoints[0]->jointName(); + bool isRightLimb = jointName.find("Right") != std::string::npos; + bool isLeftLimb = jointName.find("Left") != std::string::npos; + + if (!(isRightLimb || isLeftLimb)) + return; + + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) + return; + + if (!mPoserAnimator.isPosingAvatar(avatar)) + return; + + mLastSelectedJoint = selectedJoints[0]; + timeFadeStartedMicrosec = gFrameTime; +} + +void FSFloaterPoser::drawOnHoverJointHint() +{ + if (!mLastSelectedJoint) + return; + + constexpr U64 GLOW_TIME_US = 300000; + U64 fadeTimeUs = gFrameTime - timeFadeStartedMicrosec; + if (fadeTimeUs > GLOW_TIME_US) + return; + + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) + return; + + if (!mPoserAnimator.isPosingAvatar(avatar)) + return; + + LLJoint* joint = avatar->getJoint(std::string(mLastSelectedJoint->jointName())); + if (!joint) + return; + + F32 alphaFade = 1.f * (GLOW_TIME_US - fadeTimeUs) / GLOW_TIME_US; + static LLUIColor mBeaconColor = LLUIColorTable::getInstance()->getColor("AreaSearchBeaconColor"); + LLColor4 beaconColour = mBeaconColor.get(); + beaconColour.setAlpha(alphaFade); + LLVector3 joint_world_position = joint->getWorldPosition(); + + static LLCachedControl beacon_line_width(gSavedSettings, "DebugBeaconLineWidth"); + gObjectList.addDebugBeacon(joint_world_position, "", beaconColour, beaconColour, beacon_line_width); +} + void FSFloaterPoser::enableVisualManipulators() { if (!gAgentAvatarp || gAgentAvatarp.isNull()) @@ -359,6 +442,9 @@ void FSFloaterPoser::onPoseFileSelect() if (!avatar) return; + if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar)) + return; + bool enableButtons = mPoserAnimator.isPosingAvatar(avatar); mLoadPosesBtn->setEnabled(enableButtons); mSavePosesBtn->setEnabled(enableButtons); @@ -409,11 +495,14 @@ void FSFloaterPoser::onClickPoseSave() mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign()); return; } - + LLVOAvatar* avatar = getUiSelectedAvatar(); - if (!avatar) - return; - + if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar)) + { + mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign()); + return; + } + // if prompts are disabled or file doesn't exist, do the save immediately: const bool prompt = gSavedSettings.getBOOL(POSER_SAVECONFIRMREQUIRED_SAVE_KEY); @@ -534,7 +623,7 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi record["startFromTeePose"]["value"] = !savingDiff; if (savingDiff) - mPoserAnimator.savePosingState(avatar, &record); + mPoserAnimator.savePosingState(avatar, false, &record); LLVector3 rotation, position, scale, zeroVector; bool baseRotationIsZero; @@ -593,7 +682,7 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi void FSFloaterPoser::onClickToggleSelectedBoneEnabled() { auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; LLVOAvatar *avatar = getUiSelectedAvatar(); @@ -617,7 +706,7 @@ void FSFloaterPoser::onClickToggleSelectedBoneEnabled() void FSFloaterPoser::onClickFlipSelectedJoints() { auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; LLVOAvatar *avatar = getUiSelectedAvatar(); @@ -676,7 +765,7 @@ void FSFloaterPoser::onClickFlipPose() void FSFloaterPoser::onClickRecaptureSelectedBones() { auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; LLVOAvatar *avatar = getUiSelectedAvatar(); @@ -694,31 +783,40 @@ void FSFloaterPoser::onClickRecaptureSelectedBones() if (currentlyPosing) continue; - mPoserAnimator.recaptureJoint(avatar, *item, getJointTranslation(item->jointName()), getJointNegation(item->jointName())); + mPoserAnimator.recaptureJoint(avatar, *item); } setSavePosesButtonText(true); refreshRotationSlidersAndSpinners(); + refreshPositionSlidersAndSpinners(); + refreshScaleSlidersAndSpinners(); refreshTrackpadCursor(); refreshTextHighlightingOnJointScrollLists(); enableOrDisableRedoAndUndoButton(); } -void FSFloaterPoser::updatePosedBones(const std::string& jointName) +void FSFloaterPoser::updatePosedBones(const std::string& jointName, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale) { - LLVOAvatar *avatar = getUiSelectedAvatar(); + LLVOAvatar* avatar = getUiSelectedAvatar(); if (!avatar) return; if (!mPoserAnimator.isPosingAvatar(avatar)) return; + bool haveImplicitPermission = havePermissionToAnimateAvatar(avatar); // self & control avatars you own + bool iCanPoseThem = havePermissionToAnimateOtherAvatar(avatar); + if (!haveImplicitPermission && !iCanPoseThem) + return; + const FSPoserAnimator::FSPoserJoint* poserJoint = mPoserAnimator.getPoserJointByName(jointName); if (!poserJoint) return; - bool savingToExternal = getSavingToBvh(); - mPoserAnimator.recaptureJointAsDelta(avatar, poserJoint, savingToExternal, getUiSelectedBoneDeflectionStyle()); + bool savingToExternal = getSavingToBvh(); + E_PoserReferenceFrame frame = getReferenceFrame(); + E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle(); + mPoserAnimator.updateJointFromManip(avatar, poserJoint, savingToExternal, defl, frame, rotation, position, scale); refreshRotationSlidersAndSpinners(); refreshPositionSlidersAndSpinners(); @@ -728,6 +826,24 @@ void FSFloaterPoser::updatePosedBones(const std::string& jointName) refreshTextHighlightingOnJointScrollLists(); } +LLQuaternion FSFloaterPoser::getManipGimbalRotation(const std::string& jointName) +{ + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) + return LLQuaternion(); + + if (!mPoserAnimator.isPosingAvatar(avatar)) + return LLQuaternion(); + + const FSPoserAnimator::FSPoserJoint* poserJoint = mPoserAnimator.getPoserJointByName(jointName); + if (!poserJoint) + return LLQuaternion(); + + E_PoserReferenceFrame frame = getReferenceFrame(); + + return mPoserAnimator.getManipGimbalRotation(avatar, poserJoint, frame); +} + void FSFloaterPoser::onClickBrowsePoseCache() { createUserPoseDirectoryIfNeeded(); @@ -930,6 +1046,9 @@ void FSFloaterPoser::timedReload() if (!avatar) return; + if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar)) + return; + if (loadPoseFromXml(avatar, mLoadPoseTimer->getPosePath(), mLoadPoseTimer->getLoadMethod())) { mLoadPoseTimer->completeLoading(); @@ -1024,6 +1143,8 @@ void FSFloaterPoser::onClickLoadHandPose(bool isRightHand) LLVOAvatar* avatar = getUiSelectedAvatar(); if (!avatar) return; + if (!havePermissionToAnimateAvatar(avatar) && !havePermissionToAnimateOtherAvatar(avatar)) + return; if (!mPoserAnimator.isPosingAvatar(avatar)) return; @@ -1233,8 +1354,8 @@ bool FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose mPoserAnimator.setRotationIsMirrored(avatar, *poserJoint, mirroredJoint); } - if (version > 6 && !startFromZeroRot) - loadSuccess = mPoserAnimator.loadPosingState(avatar, pose); + if (version > 6 && !startFromZeroRot && !loadSelective) + loadSuccess = mPoserAnimator.loadPosingState(avatar, true, pose); } } catch ( const std::exception & e ) @@ -1310,7 +1431,7 @@ void FSFloaterPoser::onPoseStartStop() onAvatarSelect(); } -bool FSFloaterPoser::couldAnimateAvatar(LLVOAvatar *avatar) const +bool FSFloaterPoser::couldAnimateAvatar(LLVOAvatar* avatar) const { if (!avatar || avatar->isDead()) return false; @@ -1320,14 +1441,31 @@ bool FSFloaterPoser::couldAnimateAvatar(LLVOAvatar *avatar) const return true; } -bool FSFloaterPoser::havePermissionToAnimateAvatar(LLVOAvatar *avatar) const +bool FSFloaterPoser::havePermissionToAnimateAvatar(LLVOAvatar* avatar) const { if (!avatar || avatar->isDead()) return false; if (avatar->isSelf()) return true; + if (avatar->isControlAvatar()) - return true; + { + LLControlAvatar* control_av = dynamic_cast(avatar); + const LLVOVolume* rootVolume = control_av->mRootVolp; + const LLViewerObject* rootEditObject = (rootVolume) ? rootVolume->getRootEdit() : NULL; + if (!rootEditObject) + return false; + + return rootEditObject->permYouOwner(); + } + + return false; +} + +bool FSFloaterPoser::havePermissionToAnimateOtherAvatar(LLVOAvatar* avatar) const +{ + if (!avatar || avatar->isDead()) + return false; return false; } @@ -1342,6 +1480,12 @@ void FSFloaterPoser::poseControlsEnable(bool enable) mLoadPosesBtn->setEnabled(enable); mSavePosesBtn->setEnabled(enable); mPoseSaveNameEditor->setEnabled(enable); + mBtnJointReset->setEnabled(enable); + mRedoChangeBtn->setEnabled(enable); + mUndoChangeBtn->setEnabled(enable); + mPositionPnl->setEnabled(enable); + mMoveTabPnl->setEnabled(enable); + mTrackballButtonPnl->setEnabled(enable); } void FSFloaterPoser::refreshJointScrollListMembers() @@ -1422,13 +1566,13 @@ void FSFloaterPoser::addHeaderRowToScrollList(const std::string& jointName, LLSc return; LLScrollListItem *hdrRow = bodyJointsScrollList->addElement(headerRow); - hdrRow->setEnabled(FALSE); + hdrRow->setEnabled(false); } LLSD FSFloaterPoser::createRowForJoint(const std::string& jointName, bool isHeaderRow) { if (jointName.empty()) - return NULL; + return {}; std::string headerValue = ""; if (isHeaderRow) @@ -1439,7 +1583,7 @@ LLSD FSFloaterPoser::createRowForJoint(const std::string& jointName, bool isHead if (hasString(parameterName)) jointValue = getString(parameterName); else - return NULL; + return {}; LLSD row; row["columns"][COL_ICON]["column"] = "icon"; @@ -1500,6 +1644,22 @@ void FSFloaterPoser::setRotationChangeButtons(bool togglingMirror, bool toggling refreshTrackpadCursor(); } +void FSFloaterPoser::onToggleRotationFrameButton(const LLUICtrl* toggleButton) +{ + if (!toggleButton) + return; + + if (bool buttonDown = toggleButton->getValue().asBoolean()) + { + mBtnAvatarFrame->setValue(toggleButton == mBtnAvatarFrame); + mBtnScreenFrame->setValue(toggleButton == mBtnScreenFrame); + mBtnWorldFrame->setValue(toggleButton == mBtnWorldFrame); + } + + FSToolCompPose::getInstance()->setReferenceFrame(getReferenceFrame()); + refreshRotationSlidersAndSpinners(); +} + void FSFloaterPoser::onUndoLastChange() { LLVOAvatar* avatar = getUiSelectedAvatar(); @@ -1510,7 +1670,7 @@ void FSFloaterPoser::onUndoLastChange() return; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; for (auto item : selectedJoints) @@ -1541,7 +1701,7 @@ void FSFloaterPoser::onSetAvatarToTpose() enableOrDisableRedoAndUndoButton(); } -void FSFloaterPoser::onResetJoint(const LLSD data) +void FSFloaterPoser::onResetJoint(const LLSD& data) { int resetType = data.asInteger(); @@ -1553,7 +1713,7 @@ void FSFloaterPoser::onResetJoint(const LLSD data) return; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; for (auto item : selectedJoints) @@ -1583,7 +1743,7 @@ void FSFloaterPoser::onRedoLastChange() return; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; for (auto item : selectedJoints) @@ -1615,7 +1775,7 @@ void FSFloaterPoser::enableOrDisableRedoAndUndoButton() return; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; bool shouldEnableRedoButton = false; @@ -1706,7 +1866,7 @@ void FSFloaterPoser::selectJointByName(const std::string& jointName) LL_WARNS() << "Joint not found: " << jointName << LL_ENDL; } -LLScrollListCtrl* FSFloaterPoser::getScrollListForTab(LLPanel * tabPanel) const +LLScrollListCtrl* FSFloaterPoser::getScrollListForTab(LLPanel* tabPanel) const { if (tabPanel == mPositionRotationPnl) { @@ -1733,7 +1893,6 @@ LLScrollListCtrl* FSFloaterPoser::getScrollListForTab(LLPanel * tabPanel) const return mCollisionVolumesScrollList; } - LL_WARNS() << "Unknown tab panel: " << tabPanel << LL_ENDL; return nullptr; } @@ -1769,8 +1928,7 @@ std::vector FSFloaterPoser::getUiSelectedPoserJo for (auto item : scrollList->getAllSelected()) { - auto* userData = static_cast(item->getUserdata()); - if (userData) + if (auto* userData = static_cast(item->getUserdata())) { joints.push_back(userData); } @@ -1781,13 +1939,16 @@ std::vector FSFloaterPoser::getUiSelectedPoserJo return joints; } -void FSFloaterPoser::updateManipWithFirstSelectedJoint(std::vector joints) const +void FSFloaterPoser::updateManipWithFirstSelectedJoint(const std::vector& joints) const { auto avatarp = getUiSelectedAvatar(); if (!avatarp) return; - if (joints.size() >= 1) + bool haveImplicitPermission = havePermissionToAnimateAvatar(avatarp); + bool iCanPoseThem = havePermissionToAnimateOtherAvatar(avatarp); + + if ((joints.size() >= 1) && (haveImplicitPermission || iCanPoseThem)) FSToolCompPose::getInstance()->setJoint(avatarp->getJoint(joints[0]->jointName())); else FSToolCompPose::getInstance()->setJoint(nullptr); @@ -2073,8 +2234,9 @@ void FSFloaterPoser::setSelectedJointsPosition(F32 x, F32 y, F32 z) if (!mPoserAnimator.isPosingAvatar(avatar)) return; - LLVector3 vec3 = LLVector3(x, y, z); - E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle(); + LLVector3 vec3 = LLVector3(x, y, z); + E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle(); + E_PoserReferenceFrame frame = getReferenceFrame(); for (auto item : getUiSelectedPoserJoints()) { @@ -2082,7 +2244,7 @@ void FSFloaterPoser::setSelectedJointsPosition(F32 x, F32 y, F32 z) if (!currentlyPosingJoint) continue; - mPoserAnimator.setJointPosition(avatar, item, vec3, defl); + mPoserAnimator.setJointPosition(avatar, item, vec3, frame, defl); } } @@ -2097,7 +2259,8 @@ void FSFloaterPoser::setSelectedJointsRotation(const LLVector3& absoluteRot, con auto selectedJoints = getUiSelectedPoserJoints(); bool savingToExternal = getSavingToBvh(); - E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle(); + E_BoneDeflectionStyles deflection = getUiSelectedBoneDeflectionStyle(); + E_PoserReferenceFrame frame = getReferenceFrame(); for (auto item : selectedJoints) { @@ -2111,14 +2274,17 @@ void FSFloaterPoser::setSelectedJointsRotation(const LLVector3& absoluteRot, con bool oppositeJointAlsoSelectedOnUi = std::find(selectedJoints.begin(), selectedJoints.end(), oppositeJoint) != selectedJoints.end(); - bool deflectionDoesOppositeLimbs = !(defl == NONE || defl == DELTAMODE); + bool deflectionDoesOppositeLimbs = !(deflection == NONE || deflection == DELTAMODE); if (oppositeJointAlsoSelectedOnUi && deflectionDoesOppositeLimbs && item->dontFlipOnMirror()) continue; } - mPoserAnimator.setJointRotation(avatar, item, absoluteRot, deltaRot, defl, - getJointTranslation(item->jointName()), getJointNegation(item->jointName()), savingToExternal, - getUiSelectedBoneRotationStyle(item->jointName())); + S32 jointNegation = getJointNegation(frame, item->jointName()); + E_BoneAxisTranslation translation = getJointTranslation(frame, item->jointName()); + E_RotationStyle style = getUiSelectedBoneRotationStyle(item->jointName()); + + mPoserAnimator.setJointRotation(avatar, item, absoluteRot, deltaRot, deflection, frame, translation, jointNegation, + savingToExternal, style); } if (savingToExternal) @@ -2134,8 +2300,9 @@ void FSFloaterPoser::setSelectedJointsScale(F32 x, F32 y, F32 z) if (!mPoserAnimator.isPosingAvatar(avatar)) return; - LLVector3 vec3 = LLVector3(x, y, z); - E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle(); + LLVector3 vec3 = LLVector3(x, y, z); + E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle(); + E_PoserReferenceFrame frame = getReferenceFrame(); for (auto item : getUiSelectedPoserJoints()) { @@ -2143,7 +2310,7 @@ void FSFloaterPoser::setSelectedJointsScale(F32 x, F32 y, F32 z) if (!currentlyPosingJoint) continue; - mPoserAnimator.setJointScale(avatar, item, vec3, defl); + mPoserAnimator.setJointScale(avatar, item, vec3, frame, defl); } } @@ -2151,7 +2318,7 @@ LLVector3 FSFloaterPoser::getRotationOfFirstSelectedJoint() const { LLVector3 rotation; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return rotation; LLVOAvatar *avatar = getUiSelectedAvatar(); @@ -2161,8 +2328,10 @@ LLVector3 FSFloaterPoser::getRotationOfFirstSelectedJoint() const if (!mPoserAnimator.isPosingAvatar(avatar)) return rotation; - rotation = mPoserAnimator.getJointRotation(avatar, *selectedJoints.front(), getJointTranslation(selectedJoints.front()->jointName()), - getJointNegation(selectedJoints.front()->jointName())); + E_PoserReferenceFrame frame = getReferenceFrame(); + + rotation = mPoserAnimator.getJointRotation(avatar, *selectedJoints.front(), getJointTranslation(frame, selectedJoints.front()->jointName()), + getJointNegation(frame, selectedJoints.front()->jointName())); return rotation; } @@ -2171,7 +2340,7 @@ LLVector3 FSFloaterPoser::getPositionOfFirstSelectedJoint() const { LLVector3 position; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return position; LLVOAvatar *avatar = getUiSelectedAvatar(); @@ -2189,7 +2358,7 @@ LLVector3 FSFloaterPoser::getScaleOfFirstSelectedJoint() const { LLVector3 scale; auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return scale; LLVOAvatar *avatar = getUiSelectedAvatar(); @@ -2210,18 +2379,25 @@ void FSFloaterPoser::onJointTabSelect() refreshTrackpadCursor(); enableOrDisableRedoAndUndoButton(); refreshScaleSlidersAndSpinners(); + markSelectedJointsToHighlight(); } -E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& jointName) const +E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(E_PoserReferenceFrame frame, const std::string& jointName) const { if (jointName.empty()) return SWAP_NOTHING; - bool hasTransformParameter = hasString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName); + std::string paramName; + if (frame == POSER_FRAME_BONE) + paramName = XML_JOINT_TRANSFORM_STRING_PREFIX + jointName; + else + paramName = XML_JOINT_FRAME_TRANSFORM_PREFIX + jointName; + + bool hasTransformParameter = hasString(paramName); if (!hasTransformParameter) return SWAP_NOTHING; - std::string paramValue = getString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName); + std::string paramValue = getString(paramName); if (strstr(paramValue.c_str(), "SWAP_YAW_AND_ROLL")) return SWAP_YAW_AND_ROLL; @@ -2237,18 +2413,24 @@ E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& joi return SWAP_NOTHING; } -S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const +S32 FSFloaterPoser::getJointNegation(E_PoserReferenceFrame frame, const std::string& jointName) const { S32 result = NEGATE_NOTHING; if (jointName.empty()) return result; - bool hasTransformParameter = hasString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName); - if (!hasTransformParameter) + std::string paramName; + if (frame == POSER_FRAME_BONE) + paramName = XML_JOINT_TRANSFORM_STRING_PREFIX + jointName; + else + paramName = XML_JOINT_FRAME_TRANSFORM_PREFIX + jointName; + + bool hasNegationParameter = hasString(paramName); + if (!hasNegationParameter) return result; - std::string paramValue = getString(XML_JOINT_TRANSFORM_STRING_PREFIX + jointName); + std::string paramValue = getString(paramName); if (strstr(paramValue.c_str(), "NEGATE_YAW")) result |= NEGATE_YAW; @@ -2262,6 +2444,23 @@ S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const return result; } +E_PoserReferenceFrame FSFloaterPoser::getReferenceFrame() const +{ + bool toggleButtonValue = mBtnScreenFrame->getValue().asBoolean(); + if (toggleButtonValue) + return POSER_FRAME_CAMERA; + + toggleButtonValue = mBtnAvatarFrame->getValue().asBoolean(); + if (toggleButtonValue) + return POSER_FRAME_AVATAR; + + toggleButtonValue = mBtnWorldFrame->getValue().asBoolean(); + if (toggleButtonValue) + return POSER_FRAME_WORLD; + + return POSER_FRAME_BONE; +} + /// /// An event handler for selecting an avatar or animesh on the POSES_AVATAR_SCROLL_LIST_NAME. /// In general this will refresh the views for joints or their proxies, and (dis/en)able elements of the view. @@ -2269,16 +2468,26 @@ S32 FSFloaterPoser::getJointNegation(const std::string& jointName) const void FSFloaterPoser::onAvatarSelect() { LLVOAvatar* avatar = getUiSelectedAvatar(); - if(avatar) - { + if (!avatar) + return; + + bool isSelf = avatar->isSelf(); + bool haveImplicitPermission = havePermissionToAnimateAvatar(avatar); // self & control avatars you own + bool haveExplicitPermission = havePermissionToAnimateOtherAvatar(avatar); // as permissions allow + + if (haveImplicitPermission || haveExplicitPermission) FSToolCompPose::getInstance()->setAvatar(avatar); - } - mStartStopPosingBtn->setEnabled(couldAnimateAvatar(avatar)); + else + FSToolCompPose::getInstance()->setAvatar(nullptr); bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar); + + mStartStopPosingBtn->setEnabled(haveImplicitPermission); mStartStopPosingBtn->setValue(arePosingSelected); - mSetToTposeButton->setEnabled(arePosingSelected); - poseControlsEnable(arePosingSelected); + + mSetToTposeButton->setEnabled(haveImplicitPermission && arePosingSelected); + poseControlsEnable(arePosingSelected && haveImplicitPermission); + refreshTextHighlightingOnAvatarScrollList(); refreshTextHighlightingOnJointScrollLists(); onJointTabSelect(); @@ -2292,7 +2501,16 @@ uuid_vec_t FSFloaterPoser::getNearbyAvatarsAndAnimeshes() const for (LLCharacter* character : LLCharacter::sInstances) { LLVOAvatar* avatar = dynamic_cast(character); - if (!havePermissionToAnimateAvatar(avatar)) + if (!avatarIsNearbyMe(avatar)) + continue; + + bool isMuted = LLMuteList::getInstance()->isMuted(avatar->getID()); + if (isMuted) + continue; + + bool isSelfOrCtrl = avatar->isControlAvatar() || avatar->isSelf(); + + if (!isSelfOrCtrl) continue; avatar_ids.emplace_back(character->getID()); @@ -2301,6 +2519,16 @@ uuid_vec_t FSFloaterPoser::getNearbyAvatarsAndAnimeshes() const return avatar_ids; } +bool FSFloaterPoser::avatarIsNearbyMe(LLCharacter* character) const +{ + if (!gAgentAvatarp || gAgentAvatarp.isNull() || !character) + return false; + + LLVector3 separationVector = character->getCharacterPosition() - gAgentAvatarp->getCharacterPosition(); + + return separationVector.magVec() < 50.f; +} + uuid_vec_t FSFloaterPoser::getCurrentlyListedAvatarsAndAnimeshes() const { uuid_vec_t avatar_ids; @@ -2351,22 +2579,18 @@ void FSFloaterPoser::onAvatarsRefresh() mAvatarSelectionScrollList->deleteSingleItem(indexToRemove); } - std::string iconCatagoryName = "Inv_BodyShape"; - if (hasString("icon_category")) - iconCatagoryName = getString("icon_category"); - std::string iconObjectName = "Inv_Object"; if (hasString("icon_object")) iconObjectName = getString("icon_object"); // Add non-Animesh avatars - for (LLCharacter *character : LLCharacter::sInstances) + for (LLCharacter* character : LLCharacter::sInstances) { LLUUID uuid = character->getID(); if (std::find(avatarsToAddToList.begin(), avatarsToAddToList.end(), uuid) == avatarsToAddToList.end()) continue; - LLVOAvatar *avatar = dynamic_cast(character); + LLVOAvatar* avatar = dynamic_cast(character); if (!couldAnimateAvatar(avatar)) continue; @@ -2377,10 +2601,16 @@ void FSFloaterPoser::onAvatarsRefresh() if (!LLAvatarNameCache::get(uuid, &av_name)) continue; + if (LLMuteList::getInstance()->isMuted(uuid)) + continue; + + if (!avatar->isSelf()) + continue; + LLSD row; row["columns"][COL_ICON]["column"] = "icon"; row["columns"][COL_ICON]["type"] = "icon"; - row["columns"][COL_ICON]["value"] = iconCatagoryName; + row["columns"][COL_ICON]["value"] = getIconNameForAvatar(avatar); row["columns"][COL_NAME]["column"] = "name"; row["columns"][COL_NAME]["value"] = av_name.getDisplayName(); row["columns"][COL_UUID]["column"] = "uuid"; @@ -2397,7 +2627,7 @@ void FSFloaterPoser::onAvatarsRefresh() if (std::find(avatarsToAddToList.begin(), avatarsToAddToList.end(), uuid) == avatarsToAddToList.end()) continue; - LLControlAvatar *avatar = dynamic_cast(character); + LLControlAvatar* avatar = dynamic_cast(character); if (!couldAnimateAvatar(avatar)) continue; @@ -2423,18 +2653,33 @@ void FSFloaterPoser::onAvatarsRefresh() refreshTextHighlightingOnAvatarScrollList(); } +std::string FSFloaterPoser::getIconNameForAvatar(LLVOAvatar* avatar) +{ + std::string iconName = "Inv_BodyShape"; + if (hasString("icon_category")) + iconName = getString("icon_category"); + + if (!avatar) + return iconName; + + if (avatar->isControlAvatar() && hasString("icon_object")) + return getString("icon_object"); + + return iconName; +} + std::string FSFloaterPoser::getControlAvatarName(const LLControlAvatar* avatar) { if (!avatar) - return ""; + return {}; - const LLVOVolume* rootVolume = avatar->mRootVolp; - const LLViewerObject* rootEditObject = (rootVolume) ? rootVolume->getRootEdit() : NULL; + const LLVOVolume* rootVolume = avatar->mRootVolp; + const LLViewerObject* rootEditObject = rootVolume ? rootVolume->getRootEdit() : nullptr; if (!rootEditObject) - return ""; + return {}; const LLViewerInventoryItem* attachedItem = - (rootEditObject->isAttachment()) ? gInventory.getItem(rootEditObject->getAttachmentItemID()) : NULL; + (rootEditObject->isAttachment()) ? gInventory.getItem(rootEditObject->getAttachmentItemID()) : nullptr; if (attachedItem) return attachedItem->getName(); @@ -2442,7 +2687,7 @@ std::string FSFloaterPoser::getControlAvatarName(const LLControlAvatar* avatar) if (rootEditObject->permYouOwner()) return avatar->getFullname(); - return ""; + return {}; } void FSFloaterPoser::refreshTextHighlightingOnAvatarScrollList() @@ -2456,10 +2701,12 @@ void FSFloaterPoser::refreshTextHighlightingOnAvatarScrollList() LLUUID selectedAvatarId = cell->getValue().asUUID(); LLVOAvatar* listAvatar = getAvatarByUuid(selectedAvatarId); + ((LLScrollListText*)listItem->getColumn(COL_ICON))->setValue(getIconNameForAvatar(listAvatar)); + if (mPoserAnimator.isPosingAvatar(listAvatar)) - ((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD); + ((LLScrollListText*)listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD); else - ((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL); + ((LLScrollListText*)listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL); } } @@ -2487,34 +2734,34 @@ void FSFloaterPoser::addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* ava if (!list) return; - std::string iconValue = ""; + std::string iconValue = ""; bool considerExternalFormatSaving = getSavingToBvh(); for (auto listItem : list->getAllData()) { - FSPoserAnimator::FSPoserJoint *poserJoint = static_cast(listItem->getUserdata()); + FSPoserAnimator::FSPoserJoint* poserJoint = static_cast(listItem->getUserdata()); if (!poserJoint) continue; ((LLScrollListText*)listItem->getColumn(COL_ICON))->setValue(getScrollListIconForJoint(avatar, *poserJoint)); if (mPoserAnimator.isPosingAvatarJoint(avatar, *poserJoint)) - ((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD); + ((LLScrollListText*)listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD); else - ((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL); + ((LLScrollListText*)listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL); } } std::string FSFloaterPoser::getScrollListIconForJoint(LLVOAvatar* avatar, FSPoserAnimator::FSPoserJoint joint) { if (!avatar) - return ""; + return {}; if (mPoserAnimator.getRotationIsWorldLocked(avatar, joint)) return tryGetString("icon_rotation_is_world_locked"); if (!getSavingToBvh()) - return ""; + return {}; if (joint.boneType() == COL_VOLUMES) return tryGetString("icon_rotation_does_not_export"); @@ -2530,10 +2777,10 @@ std::string FSFloaterPoser::getScrollListIconForJoint(LLVOAvatar* avatar, FSPose return tryGetString("icon_rotation_bvh_unlocked"); } -std::string FSFloaterPoser::tryGetString(std::string name) +std::string FSFloaterPoser::tryGetString(std::string_view name) { if (name.empty()) - return ""; + return {}; return hasString(name) ? getString(name) : ""; } @@ -2560,8 +2807,7 @@ bool FSFloaterPoser::savePoseToBvh(LLVOAvatar* avatar, const std::string& poseFi std::string fullSavePath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY, poseFileName + POSE_EXTERNAL_FORMAT_FILE_EXT); - llofstream file; - file.open(fullSavePath.c_str()); + llofstream file(fullSavePath.c_str()); if (!file.is_open()) { LL_WARNS("Poser") << "Unable to save pose!" << LL_ENDL; @@ -2658,7 +2904,7 @@ void FSFloaterPoser::writeBvhFragment(llofstream* fileStream, LLVOAvatar* avatar { for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = mPoserAnimator.getPoserJointByName(joint->bvhChildren()[index]); + auto nextJoint = mPoserAnimator.getPoserJointByName(joint->bvhChildren().at(index)); writeBvhFragment(fileStream, avatar, nextJoint, tabStops + 1); } } @@ -2693,7 +2939,7 @@ void FSFloaterPoser::writeFirstFrameOfBvhMotion(llofstream* fileStream, const FS size_t numberOfBvhChildNodes = joint->bvhChildren().size(); for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = mPoserAnimator.getPoserJointByName(joint->bvhChildren()[index]); + auto nextJoint = mPoserAnimator.getPoserJointByName(joint->bvhChildren().at(index)); writeFirstFrameOfBvhMotion(fileStream, nextJoint); } } @@ -2722,15 +2968,15 @@ void FSFloaterPoser::writeBvhMotion(llofstream* fileStream, LLVOAvatar* avatar, size_t numberOfBvhChildNodes = joint->bvhChildren().size(); for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = mPoserAnimator.getPoserJointByName(joint->bvhChildren()[index]); + auto nextJoint = mPoserAnimator.getPoserJointByName(joint->bvhChildren().at(index)); writeBvhMotion(fileStream, avatar, nextJoint); } } std::string FSFloaterPoser::positionToString(const LLVector3& val) { - const float metresToInches = 39.37008f; - return std::to_string(metresToInches * val[VY]) + " " + std::to_string(metresToInches * val[VZ]) + " " + std::to_string(metresToInches * val[VX]); + constexpr F32 metersToInches = 39.37008f; + return std::to_string(metersToInches * val[VY]) + " " + std::to_string(metersToInches * val[VZ]) + " " + std::to_string(metersToInches * val[VX]); } std::string FSFloaterPoser::rotationToString(const LLVector3& val) @@ -2797,7 +3043,7 @@ S32 FSFloaterPoser::getBvhJointNegation(const std::string& jointName) const return result; } -bool FSFloaterPoser::getSavingToBvh() +bool FSFloaterPoser::getSavingToBvh() const { return gSavedSettings.getBOOL(POSER_SAVEEXTERNALFORMAT_SAVE_KEY); } @@ -2811,7 +3057,7 @@ void FSFloaterPoser::onClickSavingToBvh() void FSFloaterPoser::onClickLockWorldRotBtn() { auto selectedJoints = getUiSelectedPoserJoints(); - if (selectedJoints.size() < 1) + if (selectedJoints.empty()) return; LLVOAvatar* avatar = getUiSelectedAvatar(); diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index 8d289f53f7..d6af0c882e 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -31,6 +31,7 @@ #include "llfloater.h" #include "lltoolmgr.h" #include "fsposeranimator.h" +#include "fsmaniprotatejoint.h" class FSVirtualTrackpad; class LLButton; @@ -80,7 +81,8 @@ class FSFloaterPoser : public LLFloater, public LLEditMenuHandler friend class LLFloaterReg; FSFloaterPoser(const LLSD &key); public: - void updatePosedBones(const std::string& jointName); + void updatePosedBones(const std::string& jointName, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale); + LLQuaternion getManipGimbalRotation(const std::string& jointName); void selectJointByName(const std::string& jointName); void undo() override { onUndoLastChange(); }; bool canUndo() const override { return true; } @@ -94,6 +96,8 @@ public: void onClose(bool app_quitting) override; void onFocusReceived() override; void onFocusLost() override; + virtual void draw() override; + /// /// Refreshes the supplied pose list from the supplued subdirectory. /// @@ -139,7 +143,7 @@ public: /// Updates the visual with the first selected joint from the supplied collection, if any. /// /// The collection of selected joints. - void updateManipWithFirstSelectedJoint(std::vector joints) const; + void updateManipWithFirstSelectedJoint(const std::vector& joints) const; /// /// Gets a detectable avatar by its UUID. @@ -181,6 +185,13 @@ public: /// A the collection of UUIDs for nearby avatars. uuid_vec_t getNearbyAvatarsAndAnimeshes() const; + /// + /// Gets whether the supplied character is within chat range of gAgentAvatar. + /// + /// The character to query whether nearby. + /// True if the supplied character is within chat range, otherwise false. + bool avatarIsNearbyMe(LLCharacter* character) const; + /// /// Gets a collection of UUIDs for avatars currently being presented on the UI. /// @@ -218,7 +229,7 @@ public: LLVector3 getPositionOfFirstSelectedJoint() const; LLVector3 getScaleOfFirstSelectedJoint() const; - LLScrollListCtrl* getScrollListForTab(LLPanel * tabPanel) const; + LLScrollListCtrl* getScrollListForTab(LLPanel* tabPanel) const; // Pose load/save void createUserPoseDirectoryIfNeeded(); void onToggleLoadSavePanel(); @@ -242,17 +253,22 @@ public: void enableVisualManipulators(); void disableVisualManipulators(); + // Visual cue for which bone is under the mouse-cursor + void drawOnHoverJointHint(); + void markSelectedJointsToHighlight(); + // UI Event Handlers void onAvatarsRefresh(); void onAvatarSelect(); void onJointTabSelect(); void onToggleMirrorChange(); void onToggleSympatheticChange(); + void onToggleRotationFrameButton(const LLUICtrl* toggleButton); void onToggleVisualManipulators(); void setRotationChangeButtons(bool mirror, bool sympathetic); void onUndoLastChange(); void onRedoLastChange(); - void onResetJoint(const LLSD data); + void onResetJoint(const LLSD& data); void onSetAvatarToTpose(); void onPoseStartStop(); void onTrackballChanged(); @@ -280,6 +296,14 @@ public: void refreshTrackpadCursor(); void enableOrDisableRedoAndUndoButton(); + + /// + /// Determines if we have permission to animate the supplied avatar. + /// + /// The avatar to animate. + /// True if we have permission to animate, otherwise false. + bool havePermissionToAnimateOtherAvatar(LLVOAvatar* avatar) const; + /// /// Determines if we have permission to animate the supplied avatar. /// @@ -306,6 +330,7 @@ public: /// This facilitates 'conceptual' conversion of Euler frame to up/down, left/right and roll and is rather subjective. /// Thus, many of these 'conversions' are backed by values in the XML. /// + /// The reference frame for the change. /// The well-known name of the joint, eg: mChest. /// The axial translation so the oily angles make better sense in terms of up/down/left/right/roll. /// @@ -313,14 +338,21 @@ public: /// No the translation isn't untangling all of that, it's not needed until it is. /// We're not landing on Mars with this code, just offering a user reasonable thumb-twiddlings. /// - E_BoneAxisTranslation getJointTranslation(const std::string& jointName) const; + E_BoneAxisTranslation getJointTranslation(E_PoserReferenceFrame frame, const std::string& jointName) const; /// /// Gets the collection of E_BoneAxisNegation values for the supplied joint. /// + /// The reference frame for the change. /// The name of the joind to get the axis transformation for. /// The kind of axis transformation to perform. - S32 getJointNegation(const std::string& jointName) const; + S32 getJointNegation(E_PoserReferenceFrame frame, const std::string& jointName) const; + + /// + /// Gets the reference frame for the rotation/position/scale change. + /// + /// The reference frame for the change. + E_PoserReferenceFrame getReferenceFrame() const; /// /// Gets the axial translation required for joints when saving to BVH. @@ -336,6 +368,13 @@ public: /// void refreshTextHighlightingOnAvatarScrollList(); + /// + /// Gets an appropriate icon for the supplied avatar, based on sharing permission. + /// + /// The avatar to get an icon for. + /// A string with the name of an icon. + std::string getIconNameForAvatar(LLVOAvatar* avatar); + /// /// Refreshes the text on all joints scroll lists based on their state. /// @@ -367,7 +406,7 @@ public: /// /// The name of the string. /// The named string, if it exists, otherwise an empty string. - std::string tryGetString(std::string name); + std::string tryGetString(std::string_view name); /// /// Gets the name of an item from the supplied object ID. @@ -383,7 +422,7 @@ public: /// Gets whether the pose should also write a BVH file when saved. /// /// True if the user wants to additionally save a BVH file, otherwise false. - bool getSavingToBvh(); + bool getSavingToBvh() const; /// /// Writes the current pose in BVH-format to the supplied stream. @@ -450,10 +489,12 @@ public: /// static F32 clipRange(F32 value); - LLToolset* mLastToolset{ nullptr }; - LLTool* mJointRotTool{ nullptr }; - - LLVector3 mLastSliderRotation; + LLToolset* mLastToolset{ nullptr }; + LLTool* mJointRotTool{ nullptr }; + + LLVector3 mLastSliderRotation; + FSPoserAnimator::FSPoserJoint* mLastSelectedJoint{ nullptr }; + U64 timeFadeStartedMicrosec; FSVirtualTrackpad* mAvatarTrackball{ nullptr }; @@ -502,6 +543,10 @@ public: LLButton* mUndoChangeBtn{ nullptr }; LLButton* mSetToTposeButton{ nullptr }; LLButton* mBtnJointRotate{ nullptr }; + LLButton* mBtnJointReset{ nullptr }; + LLButton* mBtnWorldFrame{ nullptr }; + LLButton* mBtnAvatarFrame{ nullptr }; + LLButton* mBtnScreenFrame{ nullptr }; LLLineEditor* mPoseSaveNameEditor{ nullptr }; @@ -516,6 +561,9 @@ public: LLPanel* mMiscJointsPnl{ nullptr }; LLPanel* mCollisionVolumesPnl{ nullptr }; LLPanel* mPosesLoadSavePnl{ nullptr }; + LLPanel* mPositionPnl{ nullptr }; + LLPanel* mMoveTabPnl{ nullptr }; + LLPanel* mTrackballButtonPnl{ nullptr }; LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr }; LLCheckBoxCtrl* mUnlockPelvisInBvhSaveCbx{ nullptr }; diff --git a/indra/newview/fsfloatersplashscreensettings.cpp b/indra/newview/fsfloatersplashscreensettings.cpp new file mode 100644 index 0000000000..13e281e23a --- /dev/null +++ b/indra/newview/fsfloatersplashscreensettings.cpp @@ -0,0 +1,109 @@ +/** + * @file fsfloatersplashscreensettings.cpp + * @brief Splash screen settings floater + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Copyright (c) 2025 The Phoenix Firestorm Project, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "fsfloatersplashscreensettings.h" +#include "llcheckboxctrl.h" +#include "llviewercontrol.h" + +FSFloaterSplashScreenSettings::FSFloaterSplashScreenSettings(const LLSD& key) : + LLFloater(key), + mHideTopBarCheck(nullptr), + mHideBlogsCheck(nullptr), + mHideDestinationsCheck(nullptr), + mUseGrayModeCheck(nullptr), + mUseHighContrastCheck(nullptr), + mUseAllCapsCheck(nullptr), + mUseLargerFontsCheck(nullptr), + mNoTransparencyCheck(nullptr) +{ +} + +FSFloaterSplashScreenSettings::~FSFloaterSplashScreenSettings() +{ +} + +bool FSFloaterSplashScreenSettings::postBuild() +{ + mHideTopBarCheck = getChild("hide_top_bar"); + mHideBlogsCheck = getChild("hide_blogs"); + mHideDestinationsCheck = getChild("hide_destinations"); + mUseGrayModeCheck = getChild("use_gray_mode"); + mUseHighContrastCheck = getChild("use_high_contrast"); + mUseAllCapsCheck = getChild("use_all_caps"); + mUseLargerFontsCheck = getChild("use_larger_fonts"); + mNoTransparencyCheck = getChild("no_transparency"); + + mHideTopBarCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mHideBlogsCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mHideDestinationsCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mUseGrayModeCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mUseHighContrastCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mUseAllCapsCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mUseLargerFontsCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + mNoTransparencyCheck->setCommitCallback(boost::bind(&FSFloaterSplashScreenSettings::onSettingChanged, this)); + + loadSettings(); + + return true; +} + +void FSFloaterSplashScreenSettings::onOpen(const LLSD& key) +{ + LLFloater::onOpen(key); + loadSettings(); +} + +void FSFloaterSplashScreenSettings::loadSettings() +{ + mHideTopBarCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenHideTopBar")); + mHideBlogsCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenHideBlogs")); + mHideDestinationsCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenHideDestinations")); + mUseGrayModeCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenUseGrayMode")); + mUseHighContrastCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenUseHighContrast")); + mUseAllCapsCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenUseAllCaps")); + mUseLargerFontsCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenUseLargerFonts")); + mNoTransparencyCheck->setValue(gSavedSettings.getBOOL("FSSplashScreenNoTransparency")); +} + +void FSFloaterSplashScreenSettings::saveSettings() +{ + gSavedSettings.setBOOL("FSSplashScreenHideTopBar", mHideTopBarCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenHideBlogs", mHideBlogsCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenHideDestinations", mHideDestinationsCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenUseGrayMode", mUseGrayModeCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenUseHighContrast", mUseHighContrastCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenUseAllCaps", mUseAllCapsCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenUseLargerFonts", mUseLargerFontsCheck->getValue().asBoolean()); + gSavedSettings.setBOOL("FSSplashScreenNoTransparency", mNoTransparencyCheck->getValue().asBoolean()); +} + +void FSFloaterSplashScreenSettings::onSettingChanged() +{ + saveSettings(); +} + diff --git a/indra/newview/fsfloatersplashscreensettings.h b/indra/newview/fsfloatersplashscreensettings.h new file mode 100644 index 0000000000..9e0f9bcc8f --- /dev/null +++ b/indra/newview/fsfloatersplashscreensettings.h @@ -0,0 +1,59 @@ +/** + * @file fsfloatersplashscreensettings.h + * @brief Splash screen settings floater + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Copyright (c) 2025 The Phoenix Firestorm Project, Inc. + * + * 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_FLOATERSPLASHSCREENSETTINGS_H +#define FS_FLOATERSPLASHSCREENSETTINGS_H + +#include "llfloater.h" + +class LLCheckBoxCtrl; + +class FSFloaterSplashScreenSettings : public LLFloater +{ +public: + FSFloaterSplashScreenSettings(const LLSD& key); + virtual ~FSFloaterSplashScreenSettings(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + void onSettingChanged(); + void loadSettings(); + void saveSettings(); + + LLCheckBoxCtrl* mHideTopBarCheck; + LLCheckBoxCtrl* mHideBlogsCheck; + LLCheckBoxCtrl* mHideDestinationsCheck; + LLCheckBoxCtrl* mUseGrayModeCheck; + LLCheckBoxCtrl* mUseHighContrastCheck; + LLCheckBoxCtrl* mUseAllCapsCheck; + LLCheckBoxCtrl* mUseLargerFontsCheck; + LLCheckBoxCtrl* mNoTransparencyCheck; +}; + +#endif // FS_FLOATERSPLASHSCREENSETTINGS_H + diff --git a/indra/newview/fsjointpose.cpp b/indra/newview/fsjointpose.cpp index b0e879545e..2d8f634c45 100644 --- a/indra/newview/fsjointpose.cpp +++ b/indra/newview/fsjointpose.cpp @@ -25,7 +25,6 @@ */ #include -#include #include "fsposingmotion.h" #include "llcharacter.h" @@ -47,23 +46,26 @@ FSJointPose::FSJointPose(LLJoint* joint, U32 usage, bool isCollisionVolume) mJointName = joint->getName(); mIsCollisionVolume = isCollisionVolume; + mJointNumber = joint->getJointNum(); - mCurrentState = FSJointState(joint); + mCurrentState = FSJointState(joint); } void FSJointPose::setPublicPosition(const LLVector3& pos) { - addStateToUndo(FSJointState(mCurrentState)); + addStateToUndo(mCurrentState); mCurrentState.mPosition.set(pos); mCurrentState.mLastChangeWasRotational = false; } void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot) { - addStateToUndo(FSJointState(mCurrentState)); + addStateToUndo(mCurrentState); if (zeroBase) zeroBaseRotation(true); + else + mCurrentState.mUserSpecifiedBaseZero = false; mCurrentState.mRotation.set(rot); mCurrentState.mLastChangeWasRotational = true; @@ -71,7 +73,7 @@ void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot) void FSJointPose::setPublicScale(const LLVector3& scale) { - addStateToUndo(FSJointState(mCurrentState)); + addStateToUndo(mCurrentState); mCurrentState.mScale.set(scale); mCurrentState.mLastChangeWasRotational = false; } @@ -79,27 +81,30 @@ void FSJointPose::setPublicScale(const LLVector3& scale) bool FSJointPose::undoLastChange() { bool changeType = mCurrentState.mLastChangeWasRotational; - mCurrentState = undoLastStateChange(FSJointState(mCurrentState)); + mCurrentState = undoLastStateChange(mCurrentState); return changeType; } void FSJointPose::redoLastChange() { - mCurrentState = redoLastStateChange(FSJointState(mCurrentState)); + mCurrentState = redoLastStateChange(mCurrentState); } void FSJointPose::resetJoint() { - addStateToUndo(FSJointState(mCurrentState)); + addStateToUndo(mCurrentState); mCurrentState.resetJoint(); mCurrentState.mLastChangeWasRotational = true; } -void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo) +void FSJointPose::addStateToUndo(const FSJointState& stateToAddToUndo) { - auto timeIntervalSinceLastChange = std::chrono::system_clock::now() - mTimeLastUpdatedCurrentState; - mTimeLastUpdatedCurrentState = std::chrono::system_clock::now(); + mModifiedThisSession = true; + + auto now = std::chrono::system_clock::now(); + auto timeIntervalSinceLastChange = now - mTimeLastUpdatedCurrentState; + mTimeLastUpdatedCurrentState = now; if (timeIntervalSinceLastChange < UndoUpdateInterval) return; @@ -119,7 +124,7 @@ void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo) mLastSetJointStates.pop_back(); } -FSJointPose::FSJointState FSJointPose::undoLastStateChange(FSJointState thingToSet) +FSJointPose::FSJointState FSJointPose::undoLastStateChange(const FSJointState& thingToSet) { if (mLastSetJointStates.empty()) return thingToSet; @@ -133,7 +138,7 @@ FSJointPose::FSJointState FSJointPose::undoLastStateChange(FSJointState thingToS return mLastSetJointStates.at(mUndoneJointStatesIndex); } -FSJointPose::FSJointState FSJointPose::redoLastStateChange(FSJointState thingToSet) +FSJointPose::FSJointState FSJointPose::redoLastStateChange(const FSJointState& thingToSet) { if (mLastSetJointStates.empty()) return thingToSet; @@ -151,40 +156,41 @@ FSJointPose::FSJointState FSJointPose::redoLastStateChange(FSJointState thingToS void FSJointPose::recaptureJoint() { - if (mIsCollisionVolume) - return; - LLJoint* joint = mJointState->getJoint(); if (!joint) return; - addStateToUndo(FSJointState(mCurrentState)); + addStateToUndo(mCurrentState); + + if (mIsCollisionVolume) + { + mCurrentState.mPosition.clear(); + mCurrentState.mScale.clear(); + } + mCurrentState = FSJointState(joint); mCurrentState.mLastChangeWasRotational = true; } -LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase) +LLQuaternion FSJointPose::updateJointAsDelta(bool zeroBase, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale) { - LLJoint* joint = mJointState->getJoint(); - if (!joint) - return LLQuaternion::DEFAULT; - - addStateToUndo(FSJointState(mCurrentState)); + addStateToUndo(mCurrentState); mCurrentState.mLastChangeWasRotational = true; - return mCurrentState.updateFromJoint(joint, zeroBase); + + return mCurrentState.updateFromJointProperties(zeroBase, rotation, position, scale); } -void FSJointPose::setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority) +void FSJointPose::setBaseRotation(const LLQuaternion& rotation, LLJoint::JointPriority priority) { mCurrentState.resetBaseRotation(rotation, priority); } -void FSJointPose::setBasePosition(LLVector3 position, LLJoint::JointPriority priority) +void FSJointPose::setBasePosition(const LLVector3& position, LLJoint::JointPriority priority) { mCurrentState.resetBasePosition(position, priority); } -void FSJointPose::setBaseScale(LLVector3 scale, LLJoint::JointPriority priority) +void FSJointPose::setBaseScale(const LLVector3& scale, LLJoint::JointPriority priority) { mCurrentState.resetBaseScale(scale, priority); } @@ -247,6 +253,7 @@ void FSJointPose::reflectRotation() if (mIsCollisionVolume) return; + mModifiedThisSession = true; mCurrentState.reflectRotation(); } diff --git a/indra/newview/fsjointpose.h b/indra/newview/fsjointpose.h index f87856795f..bc551afbdf 100644 --- a/indra/newview/fsjointpose.h +++ b/indra/newview/fsjointpose.h @@ -97,6 +97,8 @@ class FSJointPose /// 'Public rotation' is the amount of rotation the user has added to the initial state. /// Public rotation is what a user may save to an external format (such as BVH). /// This distinguishes 'private' rotation, which is the state inherited from something like a pose in-world. + /// If zeroBase is true, we treat rotations as if in BVH mode: user work. + /// If zeroBase is false, we treat as NOT BVH: some existing pose and user work. /// void setPublicRotation(bool zeroBase, const LLQuaternion& rot); @@ -173,29 +175,32 @@ class FSJointPose /// Recalculates the delta reltive to the base for a new rotation. /// /// Whether to zero the base rotation on setting the supplied rotation. + /// The rotation of the supplied joint. + /// The position of the supplied joint. + /// The scale of the supplied joint. /// The rotation of the public difference between before and after recapture. - LLQuaternion recaptureJointAsDelta(bool zeroBase); + LLQuaternion updateJointAsDelta(bool zeroBase, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale); /// /// Sets the base rotation to the supplied rotation if the supplied priority is appropriate. /// /// The base rotation to set; zero is ignored. /// The priority of the base rotation; only priority equal or higher than any prior sets have any effect. - void setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority); + void setBaseRotation(const LLQuaternion& rotation, LLJoint::JointPriority priority); /// /// Sets the base position to the supplied position if the supplied priority is appropriate. /// /// The base position to set; zero is ignored. /// The priority of the base rotation; only priority equal or higher than any prior sets have any effect. - void setBasePosition(LLVector3 position, LLJoint::JointPriority priority); + void setBasePosition(const LLVector3& position, LLJoint::JointPriority priority); /// /// Sets the base scale to the supplied scale if the supplied priority is appropriate. /// /// The base scale to set; zero is ignored. /// The priority of the base rotation; only priority equal or higher than any prior sets have any effect. - void setBaseScale(LLVector3 scale, LLJoint::JointPriority priority); + void setBaseScale(const LLVector3& scale, LLJoint::JointPriority priority); /// /// Sets the priority of the bone to the supplied value. @@ -253,6 +258,18 @@ class FSJointPose /// LLPointer getJointState() const { return mJointState; } + /// + /// Gets whether this joint has been modified this session. + /// + /// True if the joint has been changed at all, otherwise false. + bool getJointModified() const { return mModifiedThisSession; } + + /// + /// Gets the number of the joint represented by this. + /// + /// The joint number, derived from LLjoint. + S32 getJointNumber() const { return mJointNumber; } + class FSJointState { public: @@ -326,15 +343,12 @@ class FSJointPose joint->setScale(mBaseScale); } - LLQuaternion updateFromJoint(LLJoint* joint, bool zeroBase) + LLQuaternion updateFromJointProperties(bool zeroBase, const LLQuaternion rotation, const LLVector3 position, const LLVector3 scale) { - if (!joint) - return LLQuaternion::DEFAULT; - LLQuaternion initalPublicRot = mRotation; LLQuaternion invRot = mBaseRotation; invRot.conjugate(); - LLQuaternion newPublicRot = joint->getRotation() * invRot; + LLQuaternion newPublicRot = rotation * invRot; if (zeroBase) { @@ -343,8 +357,8 @@ class FSJointPose } mRotation.set(newPublicRot); - mPosition.set(joint->getPosition() - mBasePosition); - mScale.set(joint->getScale() - mBaseScale); + mPosition.set(position - mBasePosition); + mScale.set(scale - mBaseScale); return newPublicRot *= ~initalPublicRot; } @@ -441,7 +455,7 @@ class FSJointPose }; private: - std::string mJointName = ""; // expected to be a match to LLJoint.getName() for a joint implementation. + std::string mJointName = ""; // expected to be a match to LLJoint.getName() for a joint implementation. LLPointer mJointState{ nullptr }; /// @@ -450,15 +464,22 @@ class FSJointPose /// bool mIsCollisionVolume{ false }; + S32 mJointNumber = -1; + + /// + /// Whether this joint has ever been changed by poser. + /// + bool mModifiedThisSession{ false }; + std::deque mLastSetJointStates; size_t mUndoneJointStatesIndex = 0; std::chrono::system_clock::time_point mTimeLastUpdatedCurrentState = std::chrono::system_clock::now(); FSJointState mCurrentState; - void addStateToUndo(FSJointState stateToAddToUndo); - FSJointState undoLastStateChange(FSJointState currentState); - FSJointState redoLastStateChange(FSJointState currentState); + void addStateToUndo(const FSJointState& stateToAddToUndo); + FSJointState undoLastStateChange(const FSJointState& currentState); + FSJointState redoLastStateChange(const FSJointState& currentState); }; #endif // FS_JOINTPPOSE_H diff --git a/indra/newview/fsmaniprotatejoint.cpp b/indra/newview/fsmaniprotatejoint.cpp index 1cdd3d47b4..e442fbab93 100644 --- a/indra/newview/fsmaniprotatejoint.cpp +++ b/indra/newview/fsmaniprotatejoint.cpp @@ -25,11 +25,8 @@ * $/LicenseInfo$ */ - #include "llviewerprecompiledheaders.h" - - #include "fsmaniprotatejoint.h" - -// library includes +#include "llviewerprecompiledheaders.h" +#include "fsmaniprotatejoint.h" #include "llmath.h" #include "llgl.h" #include "llrender.h" @@ -37,11 +34,10 @@ #include "llprimitive.h" #include "llview.h" #include "llfontgl.h" - #include "llrendersphere.h" #include "llvoavatar.h" #include "lljoint.h" -#include "llagent.h" // for gAgent, etc. +#include "llagent.h" // for gAgent, etc. #include "llagentcamera.h" #include "llappviewer.h" #include "llcontrol.h" @@ -173,15 +169,18 @@ static void renderStaticSphere(const LLVector3& joint_world_position, const LLCo } } - bool FSManipRotateJoint::isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera, F32& outRayDistanceFromCenter) const { - // LL_INFOS("FSManipRotateJoint") << "Checking mouse("<< mouseX << "," << mouseY << ") over joint at: " << jointWorldPos << LL_ENDL; - - auto joint_center = gAgent.getPosGlobalFromAgent( jointWorldPos ); + if (!mJoint || !mAvatar) + return false; + + if (mAvatar->isDead() || !mAvatar->isFullyLoaded()) + return false; + + auto joint_center = mAvatar->getPosGlobalFromAgent(jointWorldPos); // centre in *agent* space - LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(joint_center); + LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(joint_center); LLVector3 ray_pt, ray_dir; LLManipRotate::mouseToRay(mouseX, mouseY, &ray_pt, &ray_dir); @@ -201,9 +200,9 @@ bool FSManipRotateJoint::isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector outDistanceFromCamera = proj_len - offset; // distance along the ray to the front intersection outRayDistanceFromCenter = offset; return true; - } + } } - return (false); + return false; } //static @@ -232,7 +231,6 @@ const std::vector FSManipRotateJoint::sSelectableJoints = { "mHipRight" }, { "mKneeRight" }, { "mAnkleRight" }, - }; const std::unordered_map FSManipRotateJoint::sRingParams = @@ -241,6 +239,7 @@ const std::unordered_mapgetEnd(); @@ -331,13 +330,14 @@ void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY) mHighlightedJoint = nullptr; // reset the highlighted joint // Iterate through the avatar's joint map. - F32 nearest_hit_distance = 0.f; - F32 nearest_ray_distance = 0.f; - LLJoint * nearest_joint = nullptr; - for ( const auto& entry : getSelectableJoints()) + F32 nearest_hit_distance = 0.f; + F32 nearest_ray_distance = 0.f; + LLJoint* nearest_joint = nullptr; + LLCachedControl target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.03f); + + for (const auto& entry : getSelectableJoints()) { - - LLJoint* joint = mAvatar->getJoint(std::string(entry)); + LLJoint* joint = mAvatar->getJoint(std::string(entry)); if (!joint) continue; @@ -347,23 +347,22 @@ void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY) // Retrieve the joint's world position (in agent space). LLVector3 jointWorldPos = joint->getWorldPosition(); - LLCachedControl target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.03f); F32 distance_from_camera; F32 distance_from_joint; - if (isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera, distance_from_joint) == true) + if (!isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera, distance_from_joint)) + continue; + + // we want to highlight the closest + // If there is no joint or this joint is a closer hit than the previous one + if (!nearest_joint || nearest_ray_distance > distance_from_camera || + (nearest_ray_distance == distance_from_camera && nearest_hit_distance > distance_from_joint)) { - // we want to highlight the closest - // If there is no joint or - // this joint is a closer hit than the previous one - if (!nearest_joint || nearest_ray_distance > distance_from_camera || - (nearest_ray_distance == distance_from_camera && nearest_hit_distance > distance_from_joint)) - { - nearest_joint = joint; - nearest_hit_distance = distance_from_joint; - nearest_ray_distance = distance_from_camera; - } + nearest_joint = joint; + nearest_hit_distance = distance_from_joint; + nearest_ray_distance = distance_from_camera; } } + mHighlightedJoint = nearest_joint; } @@ -376,19 +375,23 @@ FSManipRotateJoint::FSManipRotateJoint(LLToolComposite* composite) void FSManipRotateJoint::setJoint(LLJoint* joint) { mJoint = joint; + if (!mJoint) + return; // Save initial rotation as baseline for delta rotation - if (mJoint) - { - mSavedJointRot = mJoint->getWorldRotation(); - mBoneAxes = computeBoneAxes(); - mNaturalAlignmentQuat = computeAlignmentQuat(mBoneAxes); - } + mSavedJointRot = getSelectedJointWorldRotation(); + mBoneAxes = computeBoneAxes(); + mNaturalAlignmentQuat = computeAlignmentQuat(mBoneAxes); } void FSManipRotateJoint::setAvatar(LLVOAvatar* avatar) { mAvatar = avatar; + if (!avatar) + mJoint = nullptr; + + if (mAvatar && mJoint) + setJoint(avatar->getJoint(mJoint->getJointNum())); } /** @@ -403,7 +406,7 @@ void FSManipRotateJoint::handleSelect() // Not entirely sure this is needed in the current implementation. if (mJoint) { - mSavedJointRot = mJoint->getWorldRotation(); + mSavedJointRot = getSelectedJointWorldRotation(); } } @@ -422,15 +425,12 @@ void FSManipRotateJoint::handleSelect() */ bool FSManipRotateJoint::updateVisiblity() { - if (!mJoint) - { - // No joint to manipulate, not visible + if (!isAvatarJointSafeToUse()) return false; - } if (!hasMouseCapture()) { - mRotationCenter = gAgent.getPosGlobalFromAgent( mJoint->getWorldPosition() ); + mRotationCenter = mAvatar->getPosGlobalFromAgent(mJoint->getWorldPosition()); mCamEdgeOn = false; } @@ -439,7 +439,7 @@ bool FSManipRotateJoint::updateVisiblity() //Assume that UI scale factor is equivalent for X and Y axis F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; - const LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal( mRotationCenter ); // Convert from world/agent to global + const LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); // Convert from world/agent to global const auto * viewer_camera = LLViewerCamera::getInstance(); visible = viewer_camera->projectPosAgentToScreen(agent_space_center, mCenterScreen ); @@ -473,7 +473,6 @@ bool FSManipRotateJoint::updateVisiblity() return visible; } - /** * @brief Updates the scale of a specific manipulator part. * @@ -500,21 +499,29 @@ void FSManipRotateJoint::renderRingPass(const RingRenderParams& params, float ra { gGL.pushMatrix(); { - // If an extra rotation is specified, apply it. - if (params.extraRotateAngle != 0.f) - { - gGL.rotatef(params.extraRotateAngle, params.extraRotateAxis.mV[0], - params.extraRotateAxis.mV[1], params.extraRotateAxis.mV[2]); - } + // If an extra rotation is specified, apply it. + if (params.extraRotateAngle != 0.f) + { + gGL.rotatef(params.extraRotateAngle, params.extraRotateAxis.mV[VX], params.extraRotateAxis.mV[VY], params.extraRotateAxis.mV[VZ]); + } // Get the appropriate scale value from mManipulatorScales. float scaleVal = 1.f; switch (params.scaleIndex) { - case 0: scaleVal = mManipulatorScales.mV[VX]; break; - case 1: scaleVal = mManipulatorScales.mV[VY]; break; - case 2: scaleVal = mManipulatorScales.mV[VZ]; break; - case 3: scaleVal = mManipulatorScales.mV[VW]; break; - default: break; + case 0: + scaleVal = mManipulatorScales.mV[VX]; + break; + case 1: + scaleVal = mManipulatorScales.mV[VY]; + break; + case 2: + scaleVal = mManipulatorScales.mV[VZ]; + break; + case 3: + scaleVal = mManipulatorScales.mV[VW]; + break; + default: + break; } gGL.scalef(scaleVal, scaleVal, scaleVal); gl_ring(radius, width, params.primaryColor, params.secondaryColor, CIRCLE_STEPS, pass); @@ -552,7 +559,7 @@ void FSManipRotateJoint::renderManipulatorRings(const LLVector3& agent_space_cen for (int pass = 0; pass < 2; ++pass) { - if( mManipPart == LL_NO_PART || mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) + if (mManipPart == LL_NO_PART || mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) { renderCenterSphere( mRadiusMeters); for (auto& ring_params : sRingParams) @@ -565,16 +572,17 @@ void FSManipRotateJoint::renderManipulatorRings(const LLVector3& agent_space_cen } renderRingPass(params, mRadiusMeters, width_meters, pass); } + if( mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) { - static const auto roll_color = LLColor4(1.f,0.65f,0.0f,0.4f); + static const auto roll_color = LLColor4(1.f, 0.65f, 0.0f, 0.4f); updateManipulatorScale(mManipPart, mManipulatorScales); gGL.pushMatrix(); { // Cancel the rotation applied earlier: LLMatrix4 inv_rot_mat(rotation); inv_rot_mat.invert(); - gGL.multMatrix((GLfloat*)inv_rot_mat.mMatrix); + gGL.multMatrix((GLfloat*)inv_rot_mat.mMatrix); renderCenterCircle( mRadiusMeters*1.2f, roll_color, roll_color ); } gGL.popMatrix(); @@ -619,7 +627,7 @@ void FSManipRotateJoint::renderCenterCircle(const F32 radius, const LLColor4& no LLVector3 rotationAxis = defaultNormal % targetNormal; // Cross product. F32 dot = defaultNormal * targetNormal; // Dot product. - F32 angle = acosf(dot) * RAD_TO_DEG; // Convert to degrees. + F32 angle = acosf(dot) * RAD_TO_DEG; // Convert to degrees. if (rotationAxis.magVec() > 0.001f) { @@ -664,16 +672,15 @@ void FSManipRotateJoint::renderCenterSphere(const F32 radius, const LLColor4& no // Use an average of the manipulator scales when no specific part is selected scale *= (mManipulatorScales.mV[VX] + mManipulatorScales.mV[VY] + mManipulatorScales.mV[VZ] + mManipulatorScales.mV[VW]) / 4.0f; } - + gGL.scalef(scale, scale, scale); gSphere.render(); - gGL.flush(); + gGL.flush(); } gGL.popMatrix(); } - /** * @brief Renders the joint rotation manipulator and associated visual elements. * @@ -697,72 +704,65 @@ void FSManipRotateJoint::renderCenterSphere(const F32 radius, const LLColor4& no */ void FSManipRotateJoint::render() { - // Early-out if no joint or avatar. - // Needs more something: if they log out while dots on them, asplode - if (!mJoint || !mAvatar || mAvatar->isDead()) - { + if (!isAvatarJointSafeToUse()) return; - } - + // update visibility and rotation center. bool activeJointVisible = updateVisiblity(); + // Setup GL state. LLGLSUIDefault gls_ui; gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); LLGLDepthTest gls_depth(GL_TRUE); LLGLEnable gl_blend(GL_BLEND); - + // Iterate through the avatar's joint map. // If a joint other than the currently selected is highlighted, render a pulsing sphere. // otherwise a small static sphere + LLCachedControl show_joint_markers(gSavedSettings, "FSManipShowJointMarkers", true); + LLVector3 jointLocation; for (const auto& entry : getSelectableJoints()) { LLJoint* joint = mAvatar->getJoint(std::string(entry)); if (!joint) continue; + // Update the joint's world matrix to ensure its position is current. joint->updateWorldMatrixParent(); joint->updateWorldMatrix(); - if( joint == mHighlightedJoint && joint != mJoint ) + if (joint == mJoint) + continue; + + jointLocation = joint->getWorldPosition(); + if (joint == mHighlightedJoint) { - renderPulsingSphere(joint->getWorldPosition()); - } - else if( joint != mJoint ) - { - // Render a static sphere for the joint being manipulated. - LLCachedControl show_joint_markers(gSavedSettings, "FSManipShowJointMarkers", true); - if(show_joint_markers) - { - renderStaticSphere(joint->getWorldPosition(), LLColor4(1.f, 0.5f, 0.f, 0.5f), 0.01f); - } + renderPulsingSphere(jointLocation); + continue; } + + if (show_joint_markers) + renderStaticSphere(jointLocation, LLColor4(1.f, 0.5f, 0.f, 0.5f), 0.01f); } if (!activeJointVisible) - { return; - } - - // Update joint world matrices. - mJoint->updateWorldMatrixParent(); - mJoint->updateWorldMatrix(); - - const LLQuaternion joint_world_rotation = mJoint->getWorldRotation(); + const LLQuaternion joint_world_rotation = getSelectedJointWorldRotation(); const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT; LLQuaternion currentLocalRot = mJoint->getRotation(); - LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot; + // Compute the final world alignment: LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot; - const LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + const LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); - LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); - LLQuaternion active_rotation = use_natural_direction? final_world_alignment : joint_world_rotation; + LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); + LLQuaternion active_rotation = use_natural_direction ? final_world_alignment : joint_world_rotation; active_rotation.normalize(); + // Render the manipulator rings in a separate function. gGL.matrixMode(LLRender::MM_MODELVIEW); renderAxes(agent_space_center, mRadiusMeters * 1.5f, active_rotation); @@ -779,13 +779,13 @@ void FSManipRotateJoint::renderAxes(const LLVector3& agent_space_center, F32 siz LLGLDepthTest gls_depth(GL_FALSE); gGL.pushMatrix(); gGL.translatef(agent_space_center.mV[VX], agent_space_center.mV[VY], agent_space_center.mV[VZ]); - + LLMatrix4 rot_mat(rotation); gGL.multMatrix((GLfloat*)rot_mat.mMatrix); gGL.begin(LLRender::LINES); - + // X-axis (Red) gGL.color4f(1.0f, 0.0f, 0.0f, 1.0f); gGL.vertex3f(-size, 0.0f, 0.0f); @@ -875,9 +875,7 @@ void FSManipRotateJoint::renderNameXYZ(const LLQuaternion& rot) S32 vertical_offset = window_center_y - VERTICAL_OFFSET; LLVector3 euler_angles; - rot.getEulerAngles(&euler_angles.mV[0], - &euler_angles.mV[1], - &euler_angles.mV[2]); + rot.getEulerAngles(&euler_angles.mV[VX], &euler_angles.mV[VY], &euler_angles.mV[VZ]); euler_angles *= RAD_TO_DEG; for (S32 i = 0; i < 3; ++i) { @@ -925,15 +923,6 @@ void FSManipRotateJoint::renderNameXYZ(const LLQuaternion& rot) base_y += 20.f; renderTextWithShadow(llformat("Joint: %s", mJoint->getName().c_str()), window_center_x - 130.f, base_y, LLColor4(1.f, 0.1f, 1.f, 1.f)); renderTextWithShadow(llformat("Manip: %s%c", getManipPartString(mManipPart).c_str(), mCamEdgeOn?'*':' '), window_center_x + 30.f, base_y, LLColor4(1.f, 1.f, .1f, 1.f)); - if (mManipPart != LL_NO_PART) - { - LL_INFOS("FSManipRotateJoint") << "Joint: " << mJoint->getName() - << ", Manip: " << getManipPartString(mManipPart) - << ", Quaternion: " << rot - << ", Euler Angles: " << mLastEuler - << ", Delta Angle: " << mLastAngle * RAD_TO_DEG - << LL_ENDL; - } } gGL.popMatrix(); @@ -954,48 +943,46 @@ void FSManipRotateJoint::renderActiveRing( F32 radius, F32 width, const LLColor4 } } - // ------------------------------------- // Overriding because the base uses mObjectSelection->getFirstMoveableObject(true) // Not sure we use it though...TBC (see mouse down on part instead) bool FSManipRotateJoint::handleMouseDown(S32 x, S32 y, MASK mask) { - if (!mJoint) - { + if (!isAvatarJointSafeToUse()) return false; - } // Highlight the manipulator as before. highlightManipulators(x, y); - if (mHighlightedPart != LL_NO_PART) + if (mHighlightedPart == LL_NO_PART) + return false; + + mManipPart = (EManipPart)mHighlightedPart; + + // Get the joint's center in agent space. + LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); + + // Use the existing function to get the intersection point. + LLVector3 intersection = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters); + + // Check if the returned intersection is valid. + if (intersection.isExactlyZero()) { - mManipPart = (EManipPart)mHighlightedPart; - - // Get the joint's center in agent space. - LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); - - // Use the existing function to get the intersection point. - LLVector3 intersection = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters); - - // Check if the returned intersection is valid. - if (intersection.isExactlyZero()) - { - // Treat this as a "raycast miss" and do not capture the mouse. - return false; - } - else - { - // Save the valid intersection point. - mInitialIntersection = intersection; - // Also store the joint's current rotation. - mSavedJointRot = mJoint->getWorldRotation(); - - // Capture the mouse for dragging. - setMouseCapture(true); - return true; - } + // Treat this as a "raycast miss" and do not capture the mouse. + return false; } + else + { + // Save the valid intersection point. + mInitialIntersection = intersection; + // Also store the joint's current rotation. + mSavedJointRot = getSelectedJointWorldRotation(); + + // Capture the mouse for dragging. + setMouseCapture(true); + return true; + } + return false; } @@ -1017,24 +1004,27 @@ bool FSManipRotateJoint::handleMouseDown(S32 x, S32 y, MASK mask) */ bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask) { - auto * poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); + // For joint manipulation, require both a valid joint and avatar. + if (!isAvatarJointSafeToUse()) + return false; + + auto* poser = LLFloaterReg::findTypedInstance("fs_poser"); + if (!poser) + return false; + // Determine which ring (axis) is under the mouse, also highlights selectable joints. highlightManipulators(x, y); - // For joint manipulation, require both a valid joint and avatar. - if (!mJoint || !mAvatar || mAvatar->isDead() || !poser) - { - return false; - } + poser->setFocus(true); S32 hit_part = mHighlightedPart; // Save the joint’s current world rotation as the basis for the drag. - mSavedJointRot = mJoint->getWorldRotation(); - + mSavedJointRot = getSelectedJointWorldRotation(); + mLastSetRotation.set(LLQuaternion()); mManipPart = (EManipPart)hit_part; // Convert rotation center from global to agent space. - LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); // based on mManipPArt (set in highlightmanipulators). decide whether we are constrained or not in the rotation if (mManipPart == LL_ROT_GENERAL) @@ -1098,35 +1088,27 @@ bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask) // We use mouseUp to update the UI, updating it during the drag is too slow. bool FSManipRotateJoint::handleMouseUp(S32 x, S32 y, MASK mask) { - - auto * poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); if (hasMouseCapture()) - { - // Update the UI, by causing it to read back the position of the selected joints and aply those relative to the base rot - if (poser && mJoint) - { - poser->updatePosedBones(mJoint->getName()); - } - - // Release mouse + { setMouseCapture(false); mManipPart = LL_NO_PART; - mLastAngle = 0.0f; + mLastAngle = 0.0f; mCamEdgeOn = false; + return true; } - else if(mHighlightedJoint) + else if (mHighlightedJoint) { + auto* poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); if (poser) - { poser->selectJointByName(mHighlightedJoint->getName()); - } + return true; } + return false; } - /** * @brief Does all the hard work of working out what inworld control we are interacting with * @@ -1143,7 +1125,7 @@ void FSManipRotateJoint::highlightManipulators(S32 x, S32 y) mHighlightedPart = LL_NO_PART; // Instead of using mObjectSelection->getFirstMoveableObject(), // simply require that the joint (and the avatar) is valid. - if (!mJoint || !mAvatar || mAvatar->isDead()) + if (!isAvatarJointSafeToUse()) { highlightHoverSpheres(x, y); gViewerWindow->setCursor(UI_CURSOR_ARROW); @@ -1153,25 +1135,24 @@ void FSManipRotateJoint::highlightManipulators(S32 x, S32 y) // Decide which rotation to use based on a user toggle. LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); // Compute the rotation center in agent space. - LLVector3 agent_space_rotation_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + LLVector3 agent_space_rotation_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); // Update joint world matrices. mJoint->updateWorldMatrixParent(); mJoint->updateWorldMatrix(); - const LLQuaternion joint_world_rotation = mJoint->getWorldRotation(); + const LLQuaternion joint_world_rotation = getSelectedJointWorldRotation(); const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT; LLQuaternion currentLocalRot = mJoint->getRotation(); - + LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot; rotatedNaturalAlignment.normalize(); // Compute the final world alignment: LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot; final_world_alignment.normalize(); - LLQuaternion joint_rot = use_natural_direction ? final_world_alignment : joint_world_rotation; // Compute the three local axes in world space. @@ -1308,14 +1289,18 @@ bool FSManipRotateJoint::handleHover(S32 x, S32 y, MASK mask) { highlightManipulators(x, y); } + return true; } LLQuaternion FSManipRotateJoint::dragUnconstrained(S32 x, S32 y) { + if (!isAvatarJointSafeToUse()) + return LLQuaternion(); + // Get the camera position and the joint’s pivot (in agent space) LLVector3 cam = gAgentCamera.getCameraPositionAgent(); - LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); // Compute the current intersection on the sphere. mMouseCur = intersectMouseWithSphere(x, y, agent_space_center, mRadiusMeters); @@ -1388,27 +1373,29 @@ static LLQuaternion extractTwist(const LLQuaternion& rot, const LLVector3& axis) LLVector3 proj = axis * dot; // proj is now purely along 'axis' // Build the “twist” quaternion from (proj, w), then renormalize - LLQuaternion twist(proj.mV[VX], - proj.mV[VY], - proj.mV[VZ], - w); + LLQuaternion twist(proj.mV[VX], proj.mV[VY], proj.mV[VZ], w); if (w < 0.f) - { + { twist = -twist; } twist.normalize(); return twist; } + LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y) { + if (!isAvatarJointSafeToUse()) + return LLQuaternion(); + // Get the constraint axis from our joint manipulator. LLVector3 constraint_axis = getConstraintAxis(); - LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + LLVector3 agent_space_center = mAvatar->getPosAgentFromGlobal(mRotationCenter); if (mCamEdgeOn) { LLQuaternion freeRot = dragUnconstrained(x, y); return extractTwist(freeRot, constraint_axis); } + // Project the current mouse position onto the plane defined by the constraint axis. LLVector3 projected_mouse; bool hit = getMousePointOnPlaneAgent(projected_mouse, x, y, agent_space_center, constraint_axis); @@ -1416,6 +1403,7 @@ LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y) { return LLQuaternion::DEFAULT; } + projected_mouse -= agent_space_center; projected_mouse.normalize(); @@ -1424,6 +1412,7 @@ LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y) initial_proj -= (initial_proj * constraint_axis) * constraint_axis; initial_proj.normalize(); + //float angle = acos(initial_proj * projected_mouse); // angle in (-pi, pi) // Compute the signed angle using atan2. // The numerator is the magnitude of the cross product projected along the constraint axis. float numerator = (initial_proj % projected_mouse) * constraint_axis; @@ -1431,14 +1420,16 @@ LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y) float denominator = initial_proj * projected_mouse; float angle = atan2(numerator, denominator); // angle in (-pi, pi) mLastAngle = angle; + return LLQuaternion(angle, constraint_axis); } void FSManipRotateJoint::drag(S32 x, S32 y) { - if (!updateVisiblity() || !mJoint) return; + if (!updateVisiblity()) + return; - LLQuaternion delta_rot; + LLQuaternion delta_send, delta_rot; if (mManipPart == LL_ROT_GENERAL) { delta_rot = dragUnconstrained(x, y); @@ -1447,10 +1438,31 @@ void FSManipRotateJoint::drag(S32 x, S32 y) { delta_rot = dragConstrained(x, y); } - - // Compose the saved joint rotation with the delta to compute the new world rotation. - LLQuaternion new_world_rot = mSavedJointRot * delta_rot; - mJoint->setWorldRotation(new_world_rot); + + delta_send.set(delta_rot); + delta_send *= ~mLastSetRotation; + mLastSetRotation.set(delta_rot); + delta_send = mSavedJointRot * delta_send * ~mSavedJointRot; + + switch (mReferenceFrame) + { + case POSER_FRAME_CAMERA: + case POSER_FRAME_AVATAR: + delta_send.conjugate(); + break; + + case POSER_FRAME_WORLD: + delta_send.mQ[VX] *= -1; + break; + + case POSER_FRAME_BONE: + default: + break; + } + + auto* poser = LLFloaterReg::findTypedInstance("fs_poser"); + if (poser && mJoint) + poser->updatePosedBones(mJoint->getName(), delta_send, LLVector3::zero, LLVector3::zero); } // set mConstrainedAxis based on mManipParat and returns it too. @@ -1478,10 +1490,10 @@ LLVector3 FSManipRotateJoint::setConstraintAxis() // Transform the local axis into world space using the joint's world rotation. if (mJoint) { - LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); + LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); LLQuaternion active_rotation; if (use_natural_direction) - { + { // Get the joint's current local rotation. LLQuaternion currentLocalRot = mJoint->getRotation(); const LLQuaternion parentWorldRot = (mJoint->getParent()) ? mJoint->getParent()->getWorldRotation() : LLQuaternion::DEFAULT; @@ -1493,12 +1505,41 @@ LLVector3 FSManipRotateJoint::setConstraintAxis() } else { - active_rotation = mJoint->getWorldRotation(); + active_rotation = getSelectedJointWorldRotation(); } axis = axis * active_rotation; axis.normalize(); } } + mConstraintAxis = axis; + return axis; } + +LLQuaternion FSManipRotateJoint::getSelectedJointWorldRotation() +{ + LLQuaternion joinRot; + if (!mJoint || !mAvatar) + return joinRot; + + auto* poser = dynamic_cast(LLFloaterReg::findInstance("fs_poser")); + if (!poser) + return joinRot; + + return poser->getManipGimbalRotation(mJoint->getName()); +} + +bool FSManipRotateJoint::isAvatarJointSafeToUse() +{ + if (!mJoint || !mAvatar) + return false; + + if (mAvatar->isDead() || !mAvatar->isFullyLoaded()) + { + setAvatar(nullptr); + return false; + } + + return true; +} diff --git a/indra/newview/fsmaniprotatejoint.h b/indra/newview/fsmaniprotatejoint.h index aaad19bfe6..522bfe390c 100644 --- a/indra/newview/fsmaniprotatejoint.h +++ b/indra/newview/fsmaniprotatejoint.h @@ -1,5 +1,5 @@ /** - * @file fsmaniproatejoint.h + * @file fsmaniprotatejoint.h * @brief custom manipulator for rotating joints * * $LicenseInfo:firstyear=2024&license=viewerlgpl$ @@ -32,11 +32,22 @@ #include "llmaniprotate.h" class LLJoint; -class LLVOAvatar; // or LLVOAvatarSelf, etc. +class LLVOAvatar; // for LLVOAvatarSelf, etc. + +/// +/// 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 F32 RADIUS_PIXELS = 100.f; // size in screen space constexpr S32 CIRCLE_STEPS = 100; constexpr F32 CIRCLE_STEP_SIZE = 2.0f * F_PI / CIRCLE_STEPS; constexpr F32 SQ_RADIUS = RADIUS_PIXELS * RADIUS_PIXELS; @@ -74,11 +85,25 @@ public: FSManipRotateJoint(LLToolComposite* composite); virtual ~FSManipRotateJoint() {} static std::string getManipPartString(EManipPart part); - // Called to designate which joint we are going to manipulate. + + /// + /// 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; @@ -138,8 +163,13 @@ private: void renderRingPass(const RingRenderParams& params, float radius, float width, int pass); void renderAxes(const LLVector3& center, F32 size, const LLQuaternion& rotation); + bool isAvatarJointSafeToUse(); + LLQuaternion getSelectedJointWorldRotation(); + float mLastAngle = 0.f; LLVector3 mConstraintAxis; + E_PoserReferenceFrame mReferenceFrame = POSER_FRAME_BONE; + LLQuaternion mLastSetRotation; }; #endif // FS_MANIP_ROTATE_JOINT_H diff --git a/indra/newview/fspanelimcontrolpanel.cpp b/indra/newview/fspanelimcontrolpanel.cpp index 88a1b22ada..17a3351845 100644 --- a/indra/newview/fspanelimcontrolpanel.cpp +++ b/indra/newview/fspanelimcontrolpanel.cpp @@ -32,6 +32,8 @@ #include "fsparticipantlist.h" #include "llagent.h" +#include "llchatentry.h" +#include "fsfloaterim.h" #include "llimview.h" #include "llspeakers.h" @@ -88,9 +90,18 @@ void FSPanelGroupControlPanel::setSessionId(const LLUUID& session_id) return; mParticipantList = new FSParticipantList(speaker_manager, getChild("grp_speakers_list"), true,false); + if (mParticipantList) + { + mParticipantList->setInsertMentionCallback(boost::bind(&FSPanelGroupControlPanel::insertMentionAtCursor, this, _1)); + } } } +void FSPanelGroupControlPanel::insertMentionAtCursor(const LLUUID& avatar_id) +{ + FSFloaterIM::getInstance(getSessionId())->findChild("chat_editor")->insertMentionAtCursor("secondlife:///app/agent/" + avatar_id.asString() + "/mention"); +} + uuid_vec_t FSPanelGroupControlPanel::getParticipants() const { return mParticipantList->getAvatarIds(); diff --git a/indra/newview/fspanelimcontrolpanel.h b/indra/newview/fspanelimcontrolpanel.h index f1877e9284..7ea3c8cedd 100644 --- a/indra/newview/fspanelimcontrolpanel.h +++ b/indra/newview/fspanelimcontrolpanel.h @@ -69,6 +69,7 @@ public: void draw() override; uuid_vec_t getParticipants() const override; + void insertMentionAtCursor(const LLUUID& url); protected: LLUUID mGroupID; diff --git a/indra/newview/fspanellogin.cpp b/indra/newview/fspanellogin.cpp index 595b47dc47..04bf085f95 100644 --- a/indra/newview/fspanellogin.cpp +++ b/indra/newview/fspanellogin.cpp @@ -929,6 +929,23 @@ void FSPanelLogin::loadLoginPage() params["noversionpopup"] = "true"; } + // Splash screen settings + static const std::pair mappings[] = { + {"FSSplashScreenHideTopBar", "hidetopbar"}, + {"FSSplashScreenHideBlogs", "hideblogs"}, + {"FSSplashScreenHideDestinations", "hidedestinations"}, + {"FSSplashScreenUseGrayMode", "usegraymode"}, + {"FSSplashScreenUseHighContrast", "usehighcontrast"}, + {"FSSplashScreenUseAllCaps", "useallcaps"}, + {"FSSplashScreenUseLargerFonts", "uselargerfonts"}, + {"FSSplashScreenNoTransparency", "notransparency"}, + }; + + for (const auto &m : mappings) + { + params[m.second] = gSavedSettings.getBOOL(m.first) ? "1" : "0"; + } + // Make an LLURI with this augmented info std::string url = login_page.scheme().empty()? login_page.authority() : login_page.scheme() + "://" + login_page.authority(); LLURI login_uri(LLURI::buildHTTP(url, diff --git a/indra/newview/fsparticipantlist.cpp b/indra/newview/fsparticipantlist.cpp index 4f3611dbc5..30229b52bc 100644 --- a/indra/newview/fsparticipantlist.cpp +++ b/indra/newview/fsparticipantlist.cpp @@ -39,6 +39,7 @@ #include "llnotificationsutil.h" #include "lloutputmonitorctrl.h" #include "llspeakers.h" +#include "llurlaction.h" #include "llviewercontrol.h" #include "llviewermenu.h" #include "llvoiceclient.h" @@ -69,6 +70,7 @@ FSParticipantList::FSParticipantList(LLSpeakerMgr* data_source, mParticipantListMenu(NULL), mExcludeAgent(exclude_agent), mValidateSpeakerCallback(NULL), + mInsertMentionCallback(NULL), mConvType(CONV_UNKNOWN) { mSpeakerAddListener = new SpeakerAddListener(*this); @@ -301,6 +303,11 @@ void FSParticipantList::setValidateSpeakerCallback(validate_speaker_callback_t c mValidateSpeakerCallback = cb; } +void FSParticipantList::setInsertMentionCallback(insert_mention_callback_t cb) +{ + mInsertMentionCallback = cb; +} + void FSParticipantList::update() { mSpeakerMgr->update(true); @@ -530,6 +537,9 @@ LLContextMenu* FSParticipantList::FSParticipantListMenu::createMenu() registrar.add("ParticipantList.ModerateVoice", boost::bind(&FSParticipantList::FSParticipantListMenu::moderateVoice, this, _2)); + registrar.add("Mention.CopyURI", boost::bind(&FSParticipantList::FSParticipantListMenu::copyURLToClipboard, this, mUUIDs.front())); + registrar.add("Mention.Chat", boost::bind(&FSParticipantList::FSParticipantListMenu::insertMentionAtCursor, this, mUUIDs.front())); + enable_registrar.add("ParticipantList.EnableItem", boost::bind(&FSParticipantList::FSParticipantListMenu::enableContextMenuItem, this, _2)); enable_registrar.add("ParticipantList.EnableItem.Moderate", boost::bind(&FSParticipantList::FSParticipantListMenu::enableModerateContextMenuItem, this, _2)); enable_registrar.add("ParticipantList.CheckItem", boost::bind(&FSParticipantList::FSParticipantListMenu::checkContextMenuItem, this, _2)); @@ -556,6 +566,19 @@ LLContextMenu* FSParticipantList::FSParticipantListMenu::createMenu() return main_menu; } +void FSParticipantList::FSParticipantListMenu::copyURLToClipboard(const LLUUID& avatar_id) +{ + LLUrlAction::copyURLToClipboard("secondlife:///app/agent/" + mUUIDs.front().asString() + "/mention"); +} + +void FSParticipantList::FSParticipantListMenu::insertMentionAtCursor(const LLUUID& avatar_id) +{ + if (mParent.mInsertMentionCallback) + { + mParent.mInsertMentionCallback(avatar_id); + } +} + void FSParticipantList::FSParticipantListMenu::show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) { if (uuids.size() == 0) return; diff --git a/indra/newview/fsparticipantlist.h b/indra/newview/fsparticipantlist.h index e206d2d72c..0427b27083 100644 --- a/indra/newview/fsparticipantlist.h +++ b/indra/newview/fsparticipantlist.h @@ -53,6 +53,7 @@ public: }; typedef boost::function validate_speaker_callback_t; + typedef boost::function insert_mention_callback_t; FSParticipantList(LLSpeakerMgr* data_source, LLAvatarList* avatar_list, @@ -94,6 +95,7 @@ public: * @see onAddItemEvent() */ void setValidateSpeakerCallback(validate_speaker_callback_t cb); + void setInsertMentionCallback(insert_mention_callback_t cb); EConversationType const getType() const { return mConvType; } @@ -240,6 +242,10 @@ protected: static void confirmMuteAllCallback(const LLSD& notification, const LLSD& response); void handleAddToContactSet(); + + // mentions support + void copyURLToClipboard(const LLUUID& avatar_id); + void insertMentionAtCursor(const LLUUID& avatar_id); }; /** @@ -300,6 +306,8 @@ private: LLPointer mSortByRecentSpeakers; validate_speaker_callback_t mValidateSpeakerCallback; + insert_mention_callback_t mInsertMentionCallback; + EConversationType mConvType; }; diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index 067c4dbdd0..31b35fa8f1 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -29,6 +29,7 @@ #include "fsposeranimator.h" #include "llcharacter.h" #include "llagent.h" +#include "llagentcamera.h" #include "fsposingmotion.h" std::map FSPoserAnimator::sAvatarIdToRegisteredAnimationId; @@ -52,6 +53,25 @@ bool FSPoserAnimator::isPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint return posingMotion->currentlyPosingJoint(jointPose); } +bool FSPoserAnimator::hasJointBeenChanged(LLVOAvatar* avatar, const FSPoserJoint& joint) +{ + if (!isAvatarSafeToUse(avatar)) + return false; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return false; + + if (posingMotion->isStopped()) + return false; + + FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); + if (!jointPose) + return false; + + return jointPose->getJointModified(); +} + void FSPoserAnimator::setPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, bool shouldPose) { if (!isAvatarSafeToUse(avatar)) @@ -212,7 +232,8 @@ LLVector3 FSPoserAnimator::getJointPosition(LLVOAvatar* avatar, const FSPoserJoi return jointPose->getPublicPosition(); } -void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_BoneDeflectionStyles style) +void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_PoserReferenceFrame frame, + E_BoneDeflectionStyles style) { if (!isAvatarSafeToUse(avatar)) return; @@ -231,7 +252,8 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j if (!jointPose) return; - LLVector3 positionDelta = jointPose->getPublicPosition() - position; + LLVector3 jointPosition = jointPose->getPublicPosition(); + LLVector3 positionDelta = jointPosition - position; switch (style) { @@ -239,13 +261,13 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j case MIRROR_DELTA: case SYMPATHETIC_DELTA: case SYMPATHETIC: - jointPose->setPublicPosition(position); + jointPose->setPublicPosition(jointPosition - positionDelta); break; case DELTAMODE: case NONE: default: - jointPose->setPublicPosition(position); + jointPose->setPublicPosition(jointPosition - positionDelta); return; } @@ -406,6 +428,7 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar) return; posingMotion->setAllRotationsToZeroAndClearUndo(); + mPosingState.purgeMotionStates(avatar); for (size_t index = 0; index != PoserJoints.size(); ++index) { @@ -420,10 +443,9 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar) posingMotion->setJointBvhLock(jointPose, false); } - } -void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) +void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint) { if (!isAvatarSafeToUse(avatar)) return; @@ -440,8 +462,9 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi setPosingAvatarJoint(avatar, joint, true); } -void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, - E_BoneDeflectionStyles style) +void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, + E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion& rotation, + const LLVector3& position, const LLVector3& scale) { if (!isAvatarSafeToUse(avatar)) return; @@ -454,27 +477,34 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi if (!jointPose) return; - LLQuaternion deltaRot = jointPose->recaptureJointAsDelta(resetBaseRotationToZero); + LLQuaternion framedRotation = changeToRotationFrame(avatar, rotation, frame, jointPose); + jointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * jointPose->getPublicRotation()); - deRotateWorldLockedDescendants(joint, posingMotion, deltaRot); + deRotateWorldLockedDescendants(joint, posingMotion, framedRotation); if (style == NONE || style == DELTAMODE) return; + auto oppositePoserJoint = getPoserJointByName(joint->mirrorJointName()); FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName()); if (!oppositeJointPose) return; + LLQuaternion mirroredRotation = LLQuaternion(-framedRotation.mQ[VX], framedRotation.mQ[VY], -framedRotation.mQ[VZ], framedRotation.mQ[VW]); switch (style) { case SYMPATHETIC: case SYMPATHETIC_DELTA: - oppositeJointPose->cloneRotationFrom(jointPose); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * oppositeJointPose->getPublicRotation()); + if (oppositePoserJoint) + deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, framedRotation); break; case MIRROR: case MIRROR_DELTA: - oppositeJointPose->mirrorRotationFrom(jointPose); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation()); + if (oppositePoserJoint) + deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation); break; default: @@ -482,6 +512,54 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi } } +LLQuaternion FSPoserAnimator::getManipGimbalRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, E_PoserReferenceFrame frame) +{ + LLQuaternion globalRot(-1.f, 0.f, 0.f, 0.f); + if (frame == POSER_FRAME_WORLD) + return globalRot; + + if (!joint) + return globalRot; + + if (!isAvatarSafeToUse(avatar)) + return globalRot; + + if (frame == POSER_FRAME_AVATAR) + { + LLJoint* pelvis = avatar->getJoint("mPelvis"); + if (pelvis) + return pelvis->getWorldRotation(); + + return globalRot; + } + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return globalRot; + + FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName()); + if (!jointPose) + return globalRot; + + LLJoint* llJoint = jointPose->getJointState()->getJoint(); + if (!llJoint) + return globalRot; + + if (frame == POSER_FRAME_BONE) + return llJoint->getWorldRotation(); + + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left(1.f, 0.f, 0.f); + LLVector3 up, jointToCameraPosition, jointPosition; + jointPosition = llJoint->getWorldPosition(); + jointToCameraPosition = jointPosition - gAgentCamera.getCameraPositionAgent(); + jointToCameraPosition.normalize(); + left.setVec(skyward % jointToCameraPosition); + up.setVec(jointToCameraPosition % left); + + return LLQuaternion(jointToCameraPosition, left, up); +} + LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const { auto rotation = getJointRotation(avatar, joint, SWAP_NOTHING, NEGATE_NOTHING); @@ -525,12 +603,13 @@ LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoi if (!jointPose) return vec3; - return translateRotationFromQuaternion(translation, negation, jointPose->getPublicRotation()); + return translateRotationFromQuaternion(jointPose, translation, negation, jointPose->getPublicRotation()); } void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation, - const LLVector3& deltaRotation, E_BoneDeflectionStyles deflectionStyle, - E_BoneAxisTranslation translation, S32 negation, bool resetBaseRotationToZero, E_RotationStyle rotationStyle) + const LLVector3& deltaRotation, E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, + E_BoneAxisTranslation translation, S32 negation, bool resetBaseRotationToZero, + E_RotationStyle rotationStyle) { if (!isAvatarSafeToUse(avatar)) return; @@ -545,13 +624,15 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j if (!jointPose) return; - LLQuaternion absRot = translateRotationToQuaternion(translation, negation, absRotation); - LLQuaternion deltaRot = translateRotationToQuaternion(translation, negation, deltaRotation); - switch (deflectionStyle) + bool translationRequiresDelta = frame != POSER_FRAME_BONE; + + LLQuaternion absRot = translateRotationToQuaternion(avatar, jointPose, frame, translation, negation, absRotation); + LLQuaternion deltaRot = translateRotationToQuaternion(avatar, jointPose, frame, translation, negation, deltaRotation); + switch (style) { case SYMPATHETIC: case MIRROR: - if (rotationStyle == DELTAIC_ROT) + if (rotationStyle == DELTAIC_ROT || translationRequiresDelta) jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation()); else jointPose->setPublicRotation(resetBaseRotationToZero, absRot); @@ -570,7 +651,7 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j case NONE: default: - if (rotationStyle == DELTAIC_ROT) + if (rotationStyle == DELTAIC_ROT || translationRequiresDelta) jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation()); else jointPose->setPublicRotation(resetBaseRotationToZero, absRot); @@ -586,8 +667,8 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j if (!oppositeJointPose) return; - LLQuaternion inv_quat = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]); - switch (deflectionStyle) + LLQuaternion mirroredRotation = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]); + switch (style) { case SYMPATHETIC: oppositeJointPose->cloneRotationFrom(jointPose); @@ -604,13 +685,13 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j case MIRROR: oppositeJointPose->mirrorRotationFrom(jointPose); if (oppositePoserJoint) - deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, inv_quat); + deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation); break; case MIRROR_DELTA: - oppositeJointPose->setPublicRotation(resetBaseRotationToZero, inv_quat * oppositeJointPose->getPublicRotation()); + oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation()); if (oppositePoserJoint) - deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, inv_quat); + deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation); break; default: @@ -709,8 +790,9 @@ void FSPoserAnimator::flipEntirePose(LLVOAvatar* avatar) } } -// from the UI to the bone, the inverse translation, the un-swap, the backwards -LLQuaternion FSPoserAnimator::translateRotationToQuaternion(E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation) +// from the UI to the bone. Bone rotations we store are relative to the skeleton (or to T-Pose, if you want to visualize). +LLQuaternion FSPoserAnimator::translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame, + E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation) { if (negation & NEGATE_ALL) { @@ -728,7 +810,7 @@ LLQuaternion FSPoserAnimator::translateRotationToQuaternion(E_BoneAxisTranslatio rotation.mV[VZ] *= -1; } - LLMatrix3 rot_mat; + LLMatrix3 rot_mat; switch (translation) { case SWAP_YAW_AND_ROLL: @@ -761,11 +843,63 @@ LLQuaternion FSPoserAnimator::translateRotationToQuaternion(E_BoneAxisTranslatio rot_quat = LLQuaternion(rot_mat) * rot_quat; rot_quat.normalize(); + rot_quat = changeToRotationFrame(avatar, rot_quat, frame, joint); + return rot_quat; } +LLQuaternion FSPoserAnimator::changeToRotationFrame(LLVOAvatar* avatar, const LLQuaternion& rotation, E_PoserReferenceFrame frame, FSJointPose* joint) +{ + if (!joint || !avatar) + return rotation; + + LLJoint* pelvis = avatar->getJoint("mPelvis"); + if (!pelvis) + return rotation; + + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left(1.f, 0.f, 0.f); + LLVector3 forwards(0.f, 1.f, 0.f); + LLVector3 up, jointToCameraPosition, jointPosition; + LLQuaternion worldRotOfWorld(forwards, left, skyward); + LLQuaternion differenceInWorldRot, rotDiffInChildFrame, worldRotOfPelvis, worldRotOfCamera; + LLQuaternion worldRotOfThisJoint = joint->getJointState()->getJoint()->getWorldRotation(); + + switch (frame) + { + case POSER_FRAME_WORLD: + differenceInWorldRot = worldRotOfThisJoint * ~worldRotOfWorld; + break; + + case POSER_FRAME_AVATAR: + worldRotOfPelvis = pelvis->getWorldRotation(); + differenceInWorldRot = worldRotOfThisJoint * ~worldRotOfPelvis; + break; + + case POSER_FRAME_CAMERA: + jointPosition = joint->getJointState()->getJoint()->getWorldPosition(); + jointToCameraPosition = jointPosition - gAgentCamera.getCameraPositionAgent(); + jointToCameraPosition.normalize(); + left.setVec(skyward % jointToCameraPosition); + up.setVec(jointToCameraPosition % left); + + worldRotOfCamera = LLQuaternion(jointToCameraPosition, left, up); + differenceInWorldRot = worldRotOfThisJoint * ~worldRotOfCamera; + break; + + case POSER_FRAME_BONE: + default: + return rotation; + } + + rotDiffInChildFrame = differenceInWorldRot * rotation * ~differenceInWorldRot; + rotDiffInChildFrame.conjugate(); + + return rotDiffInChildFrame; +} + // from the bone to the UI; this is the 'forwards' use of the enum -LLVector3 FSPoserAnimator::translateRotationFromQuaternion(E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const +LLVector3 FSPoserAnimator::translateRotationFromQuaternion(FSJointPose* joint, E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const { LLVector3 vec3; @@ -833,7 +967,8 @@ LLVector3 FSPoserAnimator::getJointScale(LLVOAvatar* avatar, const FSPoserJoint& return jointPose->getPublicScale(); } -void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_BoneDeflectionStyles style) +void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_PoserReferenceFrame frame, + E_BoneDeflectionStyles style) { if (!isAvatarSafeToUse(avatar)) return; @@ -852,25 +987,46 @@ void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* join if (!jointPose) return; - jointPose->setPublicScale(scale); - FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName()); - if (!oppositeJointPose) - return; + LLVector3 jointScale = jointPose->getPublicScale(); + LLVector3 scaleDelta = jointScale - scale; switch (style) { - case SYMPATHETIC: case MIRROR: - case SYMPATHETIC_DELTA: case MIRROR_DELTA: - oppositeJointPose->setPublicScale(scale); + case SYMPATHETIC_DELTA: + case SYMPATHETIC: + jointPose->setPublicScale(jointScale - scaleDelta); break; case DELTAMODE: case NONE: default: + jointPose->setPublicScale(jointScale - scaleDelta); return; } + + FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName()); + if (!oppositeJointPose) + return; + + LLVector3 oppositeJointScale = oppositeJointPose->getPublicScale(); + + switch (style) + { + case MIRROR: + case MIRROR_DELTA: + oppositeJointPose->setPublicScale(oppositeJointScale + scaleDelta); + break; + + case SYMPATHETIC_DELTA: + case SYMPATHETIC: + oppositeJointPose->setPublicScale(oppositeJointScale - scaleDelta); + break; + + default: + break; + } } bool FSPoserAnimator::tryGetJointSaveVectors(LLVOAvatar* avatar, const FSPoserJoint& joint, LLVector3* rot, LLVector3* pos, @@ -914,7 +1070,7 @@ void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint* jointPose->purgeUndoQueue(); - LLQuaternion rot = translateRotationToQuaternion(SWAP_NOTHING, NEGATE_NOTHING, rotation); + LLQuaternion rot = translateRotationToQuaternion(avatar, jointPose, POSER_FRAME_BONE, SWAP_NOTHING, NEGATE_NOTHING, rotation); jointPose->setPublicRotation(setBaseToZero, rot); } @@ -963,13 +1119,21 @@ void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joi } } -bool FSPoserAnimator::loadPosingState(LLVOAvatar* avatar, LLSD pose) +bool FSPoserAnimator::loadPosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose) { if (!isAvatarSafeToUse(avatar)) return false; mPosingState.purgeMotionStates(avatar); - mPosingState.restoreMotionStates(avatar, pose); + mPosingState.restoreMotionStates(avatar, ignoreOwnership, pose); + + return applyStatesToPosingMotion(avatar); +} + +bool FSPoserAnimator::applyStatesToPosingMotion(LLVOAvatar* avatar) +{ + if (!isAvatarSafeToUse(avatar)) + return false; FSPosingMotion* posingMotion = getPosingMotion(avatar); if (!posingMotion) @@ -1007,9 +1171,9 @@ void FSPoserAnimator::applyJointMirrorToBaseRotations(FSPosingMotion* posingMoti } } -void FSPoserAnimator::savePosingState(LLVOAvatar* avatar, LLSD* saveRecord) +void FSPoserAnimator::savePosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord) { - mPosingState.writeMotionStates(avatar, saveRecord); + mPosingState.writeMotionStates(avatar, ignoreOwnership, saveRecord); } const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) const @@ -1023,6 +1187,39 @@ const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const return nullptr; } +const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByNumber(LLVOAvatar* avatar, const S32 jointNumber) const +{ + if (!avatar) + return nullptr; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return nullptr; + + FSJointPose* parentJoint = posingMotion->getJointPoseByJointNumber(jointNumber); + if (!parentJoint) + return nullptr; + + return getPoserJointByName(parentJoint->jointName()); +} + +bool FSPoserAnimator::tryGetJointNumber(LLVOAvatar* avatar, const FSPoserJoint &poserJoint, S32& jointNumber) +{ + if (!avatar) + return false; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return false; + + FSJointPose* parentJoint = posingMotion->getJointPoseByJointName(poserJoint.jointName()); + if (!parentJoint) + return false; + + jointNumber = parentJoint->getJointNumber(); + return jointNumber >= 0; +} + bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar) { if (!isAvatarSafeToUse(avatar)) @@ -1038,7 +1235,6 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar) gAgent.stopFidget(); mPosingState.captureMotionStates(avatar); - avatar->startDefaultMotions(); avatar->startMotion(posingMotion->motionId()); @@ -1048,7 +1244,7 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar) return false; } -void FSPoserAnimator::updatePosingState(LLVOAvatar* avatar, std::vector jointsRecaptured) +void FSPoserAnimator::updatePosingState(LLVOAvatar* avatar, const std::vector& jointsRecaptured) { if (!avatar) return; @@ -1057,14 +1253,20 @@ void FSPoserAnimator::updatePosingState(LLVOAvatar* avatar, std::vector jointNumbersRecaptured; for (auto item : jointsRecaptured) - jointNamesRecaptured += item->jointName(); + { + auto poserJoint = posingMotion->getJointPoseByJointName(item->jointName()); + if (!poserJoint) + continue; - mPosingState.updateMotionStates(avatar, posingMotion, jointNamesRecaptured); + jointNumbersRecaptured.push_back(poserJoint->getJointNumber()); + } + + mPosingState.updateMotionStates(avatar, posingMotion, jointNumbersRecaptured); } -void FSPoserAnimator::stopPosingAvatar(LLVOAvatar *avatar) +void FSPoserAnimator::stopPosingAvatar(LLVOAvatar* avatar) { if (!avatar || avatar->isDead()) return; @@ -1128,7 +1330,7 @@ bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const return true; } -int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, int depth) const +int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, S32 depth) const { size_t numberOfBvhChildNodes = joint->bvhChildren().size(); if (numberOfBvhChildNodes < 1) @@ -1138,7 +1340,7 @@ int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, int depth) co for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = getPoserJointByName(joint->bvhChildren()[index]); + auto nextJoint = getPoserJointByName(joint->bvhChildren().at(index)); if (!nextJoint) continue; @@ -1162,7 +1364,7 @@ void FSPoserAnimator::deRotateWorldLockedDescendants(const FSPoserJoint* joint, for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = getPoserJointByName(joint->bvhChildren()[index]); + auto nextJoint = getPoserJointByName(joint->bvhChildren().at(index)); if (!nextJoint) continue; @@ -1193,7 +1395,7 @@ void FSPoserAnimator::deRotateJointOrFirstLockedChild(const FSPoserJoint* joint, for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = getPoserJointByName(joint->bvhChildren()[index]); + auto nextJoint = getPoserJointByName(joint->bvhChildren().at(index)); if (!nextJoint) continue; @@ -1209,7 +1411,7 @@ void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = getPoserJointByName(joint.bvhChildren()[index]); + auto nextJoint = getPoserJointByName(joint.bvhChildren().at(index)); if (!nextJoint) continue; @@ -1219,6 +1421,9 @@ void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo) { + if (!posingMotion) + return; + FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); if (!jointPose) return; @@ -1239,7 +1444,7 @@ void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& join for (size_t index = 0; index != numberOfBvhChildNodes; ++index) { - auto nextJoint = getPoserJointByName(joint.bvhChildren()[index]); + auto nextJoint = getPoserJointByName(joint.bvhChildren().at(index)); if (!nextJoint) continue; diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 880997a750..2f7cb32948 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -30,6 +30,7 @@ #include "fsposingmotion.h" #include "fsposestate.h" #include "llvoavatar.h" +#include "fsmaniprotatejoint.h" /// /// Describes how we will cluster the joints/bones/thingos. @@ -360,31 +361,31 @@ public: { "mSpine4", "", MISC, { "mChest", }, "-0.205 0.015 0.000" }, // Collision Volumes - { "HEAD", "", COL_VOLUMES }, - { "NECK", "", COL_VOLUMES }, - { "L_CLAVICLE", "R_CLAVICLE", COL_VOLUMES }, - { "R_CLAVICLE", "L_CLAVICLE", COL_VOLUMES, {}, "", "", true }, - { "CHEST", "", COL_VOLUMES }, - { "LEFT_PEC", "RIGHT_PEC", COL_VOLUMES }, - { "RIGHT_PEC", "LEFT_PEC", COL_VOLUMES, {}, "", "", true }, - { "UPPER_BACK", "", COL_VOLUMES }, - { "LEFT_HANDLE", "RIGHT_HANDLE", COL_VOLUMES }, - { "RIGHT_HANDLE", "LEFT_HANDLE", COL_VOLUMES, {}, "", "", true }, - { "BELLY", "", COL_VOLUMES }, - { "PELVIS", "", COL_VOLUMES }, - { "BUTT", "", COL_VOLUMES }, - { "L_UPPER_ARM", "R_UPPER_ARM", COL_VOLUMES }, - { "R_UPPER_ARM", "L_UPPER_ARM", COL_VOLUMES, {}, "", "", true }, - { "L_LOWER_ARM", "R_LOWER_ARM", COL_VOLUMES }, - { "R_LOWER_ARM", "L_LOWER_ARM", COL_VOLUMES, {}, "", "", true }, - { "L_HAND", "R_HAND", COL_VOLUMES }, - { "R_HAND", "L_HAND", COL_VOLUMES, {}, "", "", true }, - { "L_UPPER_LEG", "R_UPPER_LEG", COL_VOLUMES }, - { "R_UPPER_LEG", "L_UPPER_LEG", COL_VOLUMES, {}, "", "", true }, - { "L_LOWER_LEG", "R_LOWER_LEG", COL_VOLUMES }, - { "R_LOWER_LEG", "L_LOWER_LEG", COL_VOLUMES, {}, "", "", true }, - { "L_FOOT", "R_FOOT", COL_VOLUMES }, - { "R_FOOT", "L_FOOT", COL_VOLUMES, {}, "", "", true }, + { "HEAD", "", COL_VOLUMES, {}, "0 0.07 0.02", "0.000 0.100 0.000" }, + { "NECK", "", COL_VOLUMES, {}, "0 0.02 0.0", "0.000 0.080 0.000" }, + { "L_CLAVICLE", "R_CLAVICLE", COL_VOLUMES, {}, "0 0.02 0.02", "0.1 0.0 0.0" }, + { "R_CLAVICLE", "L_CLAVICLE", COL_VOLUMES, {}, "0 0.02 0.02", "-0.1 0.0 0.0", true }, + { "CHEST", "", COL_VOLUMES, {}, "0 0.07 0.028", "0.000 0.152 -0.096" }, + { "LEFT_PEC", "RIGHT_PEC", COL_VOLUMES, {}, "0.082 0.042 0.119", "0.000 -0.006 0.080" }, + { "RIGHT_PEC", "LEFT_PEC", COL_VOLUMES, {}, "-0.082 0.042 0.119", "0.000 -0.006 0.080", true }, + { "UPPER_BACK", "", COL_VOLUMES, {}, "0.0 0.017 0.0", "0.0 0.0 -0.100" }, + { "LEFT_HANDLE", "RIGHT_HANDLE", COL_VOLUMES, {}, "0.10 0.058 0.0", "0.100 0.000 0.000" }, + { "RIGHT_HANDLE", "LEFT_HANDLE", COL_VOLUMES, {}, "-0.10 0.058 0.0", "-0.100 0.000 0.000", true }, + { "BELLY", "", COL_VOLUMES, {}, "0 0.04 0.028", "0.000 0.094 0.028" }, + { "PELVIS", "", COL_VOLUMES, {}, "0 -0.02 -0.01", "0.000 0.095 0.030" }, + { "BUTT", "", COL_VOLUMES, {}, "0 -0.1 -0.06", "0.000 0.000 -0.100" }, + { "L_UPPER_ARM", "R_UPPER_ARM", COL_VOLUMES, {}, "0.12 0.01 0.0", "0.130 -0.003 0.000" }, + { "R_UPPER_ARM", "L_UPPER_ARM", COL_VOLUMES, {}, "-0.12 0.01 0.0", "-0.130 -0.003 0.000", true }, + { "L_LOWER_ARM", "R_LOWER_ARM", COL_VOLUMES, {}, "0.1 0.0 0.0", "0.100 -0.001 0.000" }, + { "R_LOWER_ARM", "L_LOWER_ARM", COL_VOLUMES, {}, "-0.1 0.0 0.0", "-0.100 -0.001 0.000", true }, + { "L_HAND", "R_HAND", COL_VOLUMES, {}, "0.05 0.0 0.01", "0.049 -0.001 0.005" }, + { "R_HAND", "L_HAND", COL_VOLUMES, {}, "-0.05 0.0 0.01", "-0.049 -0.001 0.005", true }, + { "L_UPPER_LEG", "R_UPPER_LEG", COL_VOLUMES, {}, "-0.05 -0.22 -0.02", "0.000 -0.200 0.000" }, + { "R_UPPER_LEG", "L_UPPER_LEG", COL_VOLUMES, {}, "0.05 -0.22 -0.02", "0.000 -0.200 0.000", true }, + { "L_LOWER_LEG", "R_LOWER_LEG", COL_VOLUMES, {}, "0.0 -0.2 -0.02", "0.000 -0.150 -0.010" }, + { "R_LOWER_LEG", "L_LOWER_LEG", COL_VOLUMES, {}, "0.0 -0.2 -0.02", "0.000 -0.150 -0.010", true }, + { "L_FOOT", "R_FOOT", COL_VOLUMES, {}, "0.0 -0.041 0.077", "0.000 -0.026 0.089" }, + { "R_FOOT", "L_FOOT", COL_VOLUMES, {}, "0.0 -0.041 0.077", "0.000 -0.026 0.089", true }, }; public: @@ -395,6 +396,20 @@ public: /// The matching joint if found, otherwise nullptr const FSPoserJoint* getPoserJointByName(const std::string& jointName) const; + /// + /// Get a PoserJoint case-insensitive-matching the supplied name. + /// + /// The name of the joint to match. + /// The matching joint if found, otherwise nullptr + const FSPoserJoint* getPoserJointByNumber(LLVOAvatar* avatar, const S32 jointNumber) const; + + /// + /// Get a PoserJoint by its LLJoint number. + /// + /// The name of the joint to match. + /// The matching joint if found, otherwise nullptr + bool tryGetJointNumber(LLVOAvatar* avatar, const FSPoserJoint &poserJoint, S32& jointNumber); + /// /// Tries to start posing the supplied avatar. /// @@ -423,6 +438,14 @@ public: /// True if this is joint is being posed for the supplied avatar, otherwise false. bool isPosingAvatarJoint(LLVOAvatar* avatar, const FSPoserJoint& joint); + /// + /// Determines whether the supplied PoserJoint for the supplied avatar has been modified this session, even if all change has been reverted. + /// + /// The avatar having the joint to which we refer. + /// The joint being queried for. + /// True if this is joint has been changed while posing even if the change has been reverted or undone, otherwise false. + bool hasJointBeenChanged(LLVOAvatar* avatar, const FSPoserJoint& joint); + /// /// Sets whether the supplied PoserJoint for the supplied avatar should be posed. /// @@ -479,8 +502,10 @@ public: /// The avatar whose joint is to be set. /// The joint to set. /// The position to set the joint to. + /// The frame to translate the position to. /// Any ancilliary action to be taken with the change to be made. - void setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_BoneDeflectionStyles style); + void setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& position, E_PoserReferenceFrame frame, + E_BoneDeflectionStyles style); /// /// Gets the rotation of a joint for the supplied avatar. @@ -506,6 +531,15 @@ public: /// LLVector3 getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const; + /// + /// Gets the rotation suitable for the Manip gimbal for the supplied avatar and joint. + /// + /// The avatar having the Manip gimbal placed upon it. + /// The joint on the avatar where the manip should be placed. + /// The frame of reference for the gimbal. + /// The rotation to set the gimbal to. + LLQuaternion getManipGimbalRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, E_PoserReferenceFrame frame); + /// /// Sets the rotation of a joint for the supplied avatar. /// @@ -518,8 +552,9 @@ public: /// The style of negation to apply to the set. /// Whether to set the base rotation to zero on setting the rotation. /// Whether to apply the supplied rotation as a delta to the supplied joint. - void setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation, const LLVector3& deltaRotation, E_BoneDeflectionStyles style, - E_BoneAxisTranslation translation, S32 negation, bool resetBaseRotationToZero, E_RotationStyle rotationStyle); + void setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& absRotation, const LLVector3& deltaRotation, + E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, E_BoneAxisTranslation translation, S32 negation, + bool resetBaseRotationToZero, E_RotationStyle rotationStyle); /// /// Gets the scale of a joint for the supplied avatar. @@ -535,8 +570,10 @@ public: /// The avatar whose joint is to be set. /// The joint to set. /// The scale to set the joint to. + /// The frame to translate the position to. /// Any ancilliary action to be taken with the change to be made. - void setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_BoneDeflectionStyles style); + void setJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, const LLVector3& scale, E_PoserReferenceFrame frame, + E_BoneDeflectionStyles style); /// /// Reflects the joint with its opposite if it has one, or just mirror the rotation of itself. @@ -564,9 +601,7 @@ public: /// /// The avatar whose joint is to be recaptured. /// The joint to recapture. - /// The axial translation form the supplied joint. - /// The style of negation to apply to the recapture. - void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation); + void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint); /// /// Recaptures any change in joint state. @@ -575,7 +610,11 @@ public: /// The joint to recapture. /// Whether to set the base rotation to zero on setting the rotation. /// Any ancilliary action to be taken with the change to be made. - void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, E_BoneDeflectionStyles style); + /// The rotation of the supplied joint. + /// The position of the supplied joint. + /// The scale of the supplied joint. + void updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, E_BoneDeflectionStyles style, + E_PoserReferenceFrame frame, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale); /// /// Sets all of the joint rotations of the supplied avatar to zero. @@ -711,6 +750,7 @@ public: /// Loads the posing state (base rotations) to the supplied avatars posing-motion, from the supplied record. /// /// That avatar whose posing state should be loaded. + /// Whether to ignore ownership. For use when reading a local file. /// The record to read the posing state from. /// True if the pose loaded successfully, otherwise false. /// @@ -718,32 +758,29 @@ public: /// it can take several frames for the animation to be loaded and ready. /// It may therefore be necessary to attempt this several times. /// - bool loadPosingState(LLVOAvatar* avatar, LLSD pose); + bool loadPosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose); + + /// + /// Applies the posing states to the posing motion for the supplied avatar. + /// + /// That avatar whose posing state should be loaded. + /// True if the state applied successfully, otherwise false. + bool applyStatesToPosingMotion(LLVOAvatar* avatar); /// /// Adds the posing state for the supplied avatar to the supplied record. /// /// That avatar whose posing state should be written. + /// Whether to ignore ownership while saving. /// The record to write the posing state to. - void savePosingState(LLVOAvatar* avatar, LLSD* saveRecord); + void savePosingState(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord); /// /// Purges and recaptures the pose state for the supplied avatar. /// /// The avatar whose pose state is to be recapture. /// The joints which were recaptured. - void updatePosingState(LLVOAvatar* avatar, std::vector jointsRecaptured); - - /// - /// Add a new posing state, or updates the matching posing state with the supplied data. - /// - /// The avatar the posing state is intended for. - /// The ID of the animation. - /// The frame-time of the animation. - /// The names of the joints, if any, the animation should specifically be applied to. - /// The capture order. - /// True if the posing state was added or changed by the update data, otherwise false. - bool addOrUpdatePosingState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder); + void updatePosingState(LLVOAvatar* avatar, const std::vector& jointsRecaptured); /// /// Traverses the joints and applies reversals to the base rotations if needed. @@ -755,14 +792,19 @@ public: void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion); private: - /// - /// Translates a rotation vector from the UI to a Quaternion for the bone. - /// This also performs the axis-swapping the UI needs for up/down/left/right to make sense. - /// - /// The axis translation to perform. - /// The rotation to transform to quaternion. - /// The rotation quaternion. - LLQuaternion translateRotationToQuaternion(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. @@ -770,7 +812,7 @@ public: /// The axis translation to perform. /// The rotation to transform to matrix. /// The rotation vector. - LLVector3 translateRotationFromQuaternion(E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const; + LLVector3 translateRotationFromQuaternion(FSJointPose* joint, E_BoneAxisTranslation translation, S32 negation, const LLQuaternion& rotation) const; /// /// Creates a posing motion for the supplied avatar. @@ -803,7 +845,7 @@ public: /// The joint to determine the depth for. /// The depth of the supplied joint. /// The number of generations of descendents the joint has, if none, then zero. - int getChildJointDepth(const FSPoserJoint* joint, int depth) const; + int getChildJointDepth(const FSPoserJoint* joint, S32 depth) const; /// /// Derotates the first world-locked child joint to the supplied joint. @@ -844,6 +886,20 @@ public: /// Whether to redo the edit, otherwise the edit is undone. void undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo); + /// + /// Converts the supplied rotation into the desired frame. + /// + /// The avatar owning the supplied joint. + /// The rotation to convert. + /// The frame to translate the rotation to. + /// The joint whose rotation is being changed. + /// + /// Input rotations have no implicit frame: it's just a rotation and ordinarily applied, inherits the joint's rotational framing. + /// This method imposes a framing upon the supplied rotation, meaning user input is considered as relative to something like + /// 'the world', 'avatar pelvis' or the position of the camera relative to the joint. + /// + LLQuaternion changeToRotationFrame(LLVOAvatar* avatar, const LLQuaternion& rotation, E_PoserReferenceFrame frame, FSJointPose* joint); + /// /// 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/fsposestate.cpp b/indra/newview/fsposestate.cpp index ea94565d7d..416f60b811 100644 --- a/indra/newview/fsposestate.cpp +++ b/indra/newview/fsposestate.cpp @@ -3,6 +3,7 @@ std::map> FSPoseState::sMotionStates; std::map FSPoseState::sCaptureOrder; +std::map FSPoseState::sMotionStatesOwnedByMe; void FSPoseState::captureMotionStates(LLVOAvatar* avatar) { @@ -10,6 +11,7 @@ void FSPoseState::captureMotionStates(LLVOAvatar* avatar) return; sCaptureOrder[avatar->getID()] = 0; + int animNumber = 0; for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); ++anim_it) { @@ -21,27 +23,26 @@ void FSPoseState::captureMotionStates(LLVOAvatar* avatar) newState.motionId = anim_it->first; newState.lastUpdateTime = motion->getLastUpdateTime(); newState.captureOrder = 0; - newState.avatarOwnsPose = canSaveMotionId(avatar, anim_it->first); + newState.inLayerOrder = animNumber++; + newState.gAgentOwnsPose = canSaveMotionId(avatar, anim_it->first); sMotionStates[avatar->getID()].push_back(newState); } } -void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured) +void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, const std::vector& jointNumbersRecaptured) { if (!avatar || !posingMotion) return; sCaptureOrder[avatar->getID()]++; + S32 animNumber = 0; - // if an animation for avatar is a subset of jointNamesRecaptured, delete it + // if an animation for avatar is a subset of jointNumbersRecaptured, delete it // this happens on second/subsequent recaptures; the first recapture is no longer needed for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end();) { - std::string joints = (*it).jointNamesAnimated; - bool recaptureMatches = !joints.empty() && !jointNamesRecaptured.empty() && jointNamesRecaptured.find(joints) != std::string::npos; - - if (recaptureMatches) + if (vector2IsSubsetOfVector1(jointNumbersRecaptured, (*it).jointNumbersAnimated)) it = sMotionStates[avatar->getID()].erase(it); else it++; @@ -53,7 +54,7 @@ void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingM if (!motion) continue; - if (!posingMotion->otherMotionAnimatesJoints(motion, jointNamesRecaptured)) + if (!posingMotion->otherMotionAnimatesJoints(motion, jointNumbersRecaptured)) continue; bool foundMatch = false; @@ -71,45 +72,17 @@ void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingM continue; fsMotionState newState; - newState.motionId = anim_it->first; - newState.lastUpdateTime = motion->getLastUpdateTime(); - newState.jointNamesAnimated = jointNamesRecaptured; - newState.captureOrder = sCaptureOrder[avatar->getID()]; - newState.avatarOwnsPose = canSaveMotionId(avatar, anim_it->first); + newState.motionId = anim_it->first; + newState.lastUpdateTime = motion->getLastUpdateTime(); + newState.jointNumbersAnimated = jointNumbersRecaptured; + newState.captureOrder = sCaptureOrder[avatar->getID()]; + newState.inLayerOrder = animNumber++; + newState.gAgentOwnsPose = canSaveMotionId(avatar, anim_it->first); sMotionStates[avatar->getID()].push_back(newState); } } -bool FSPoseState::addOrUpdatePosingMotionState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder) -{ - if (!avatar) - return false; - - bool foundMatch = false; - for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++) - { - bool motionIdMatches = (*it).motionId == animId; - bool updateTimesMatch = (*it).lastUpdateTime == updateTime; - bool jointNamesMatch = (*it).jointNamesAnimated == jointNames; - bool captureOrdersMatch = (*it).captureOrder == captureOrder; - - foundMatch = motionIdMatches && updateTimesMatch && jointNamesMatch && captureOrdersMatch; - if (foundMatch) - return false; - } - - fsMotionState newState; - newState.motionId = animId; - newState.lastUpdateTime = updateTime; - newState.jointNamesAnimated = jointNames; - newState.captureOrder = captureOrder; - newState.avatarOwnsPose = false; - - sMotionStates[avatar->getID()].push_back(newState); - return true; -} - void FSPoseState::purgeMotionStates(LLVOAvatar* avatar) { if (!avatar) @@ -118,26 +91,35 @@ void FSPoseState::purgeMotionStates(LLVOAvatar* avatar) sMotionStates[avatar->getID()].clear(); } -void FSPoseState::writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord) +void FSPoseState::writeMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord) { if (!avatar) return; - int animNumber = 0; + S32 animNumber = 0; for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); ++it) { - if (!it->avatarOwnsPose) - continue; + if (!ignoreOwnership && !it->gAgentOwnsPose) + { + if (it->requeriedAssetInventory) + continue; - std::string uniqueAnimId = "poseState" + std::to_string(animNumber++); - (*saveRecord)[uniqueAnimId]["animationId"] = it->motionId.asString(); - (*saveRecord)[uniqueAnimId]["lastUpdateTime"] = it->lastUpdateTime; - (*saveRecord)[uniqueAnimId]["jointNamesAnimated"] = it->jointNamesAnimated; - (*saveRecord)[uniqueAnimId]["captureOrder"] = it->captureOrder; + it->gAgentOwnsPose = canSaveMotionId(avatar, it->motionId); + it->requeriedAssetInventory = true; + if (!it->gAgentOwnsPose) + continue; + } + + std::string uniqueAnimId = "poseState" + std::to_string(animNumber++); + (*saveRecord)[uniqueAnimId]["animationId"] = it->motionId.asString(); + (*saveRecord)[uniqueAnimId]["lastUpdateTime"] = it->lastUpdateTime; + (*saveRecord)[uniqueAnimId]["jointNumbersAnimated"] = encodeVectorToString(it->jointNumbersAnimated); + (*saveRecord)[uniqueAnimId]["captureOrder"] = it->captureOrder; + (*saveRecord)[uniqueAnimId]["inLayerOrder"] = it->inLayerOrder; } } -void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, LLSD pose) +void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose) { if (!avatar) return; @@ -153,25 +135,33 @@ void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, LLSD pose) continue; fsMotionState newState; - newState.avatarOwnsPose = true; if (control_map.has("animationId")) { std::string const name = control_map["animationId"].asString(); LLUUID animId; if (LLUUID::parseUUID(name, &animId)) + { newState.motionId = animId; + newState.gAgentOwnsPose = ignoreOwnership || canSaveMotionId(avatar, animId); + + if (ignoreOwnership) + sMotionStatesOwnedByMe[animId] = true; + } } if (control_map.has("lastUpdateTime")) newState.lastUpdateTime = (F32)control_map["lastUpdateTime"].asReal(); - if (control_map.has("jointNamesAnimated")) - newState.jointNamesAnimated = control_map["jointNamesAnimated"].asString(); + if (control_map.has("jointNumbersAnimated")) + newState.jointNumbersAnimated = decodeStringToVector(control_map["jointNumbersAnimated"].asString()); if (control_map.has("captureOrder")) newState.captureOrder = control_map["captureOrder"].asInteger(); + if (control_map.has("inLayerOrder")) + newState.inLayerOrder = control_map["inLayerOrder"].asInteger(); + if (newState.captureOrder > sCaptureOrder[avatar->getID()]) sCaptureOrder[avatar->getID()] = newState.captureOrder; @@ -188,7 +178,7 @@ bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMo std::sort(sMotionStates[avatar->getID()].begin(), sMotionStates[avatar->getID()].end(), compareByCaptureOrder()); - int lastCaptureOrder = 0; + S32 lastCaptureOrder = 0; bool needPriorityReset = false; for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++) { @@ -207,7 +197,7 @@ bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMo resetPriorityForCaptureOrder(avatar, posingMotion, lastCaptureOrder); } - it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNamesAnimated); + it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNumbersAnimated); } else { @@ -221,30 +211,50 @@ bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMo return allMotionsApplied; } -void FSPoseState::resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder) +void FSPoseState::resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, S32 captureOrder) { for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++) { - if (it->jointNamesAnimated.empty()) + if (it->jointNumbersAnimated.empty()) continue; if (it->motionApplied) continue; if (it->captureOrder != captureOrder) continue; - posingMotion->resetBonePriority(it->jointNamesAnimated); + posingMotion->resetBonePriority(it->jointNumbersAnimated); } } -bool FSPoseState::canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId) +bool FSPoseState::canSaveMotionId(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId) { if (!gAgentAvatarp || gAgentAvatarp.isNull()) return false; + if (sMotionStatesOwnedByMe[motionId]) + return true; + // does the animation exist in inventory LLInventoryItem* item = gInventory.getItem(motionId); - if (item && item->getPermissions().getOwner() == avatar->getID()) - return true; + if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID()) + { + sMotionStatesOwnedByMe[motionId] = true; + return sMotionStatesOwnedByMe[motionId]; + } + + if (!avatarPlayingMotionId) + return false; + + if (avatarPlayingMotionId->getID() == gAgentAvatarp->getID()) + return motionIdIsAgentAnimationSource(motionId); + + return motionIdIsFromPrimAgentOwnsAgentIsSittingOn(avatarPlayingMotionId, motionId); +} + +bool FSPoseState::motionIdIsAgentAnimationSource(LLAssetID motionId) +{ + if (!gAgentAvatarp || gAgentAvatarp.isNull()) + return false; for (const auto& [anim_object_id, anim_anim_id] : gAgentAvatarp->mAnimationSources) { @@ -252,17 +262,150 @@ bool FSPoseState::canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId) continue; // is the item that started the anim in inventory - item = gInventory.getItem(anim_object_id); - if (item && item->getPermissions().getOwner() == avatar->getID()) - return true; + LLInventoryItem* item = gInventory.getItem(anim_object_id); + if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID()) + { + sMotionStatesOwnedByMe[motionId] = true; + return sMotionStatesOwnedByMe[motionId]; + } // is the item that start the animation in-world LLViewerObject* object = gObjectList.findObject(anim_object_id); if (object && object->permYouOwner()) - return true; - - return false; + { + sMotionStatesOwnedByMe[motionId] = true; + return sMotionStatesOwnedByMe[motionId]; + } } return false; } + +bool FSPoseState::motionIdIsFromPrimAgentOwnsAgentIsSittingOn(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId) +{ + if (!avatarPlayingMotionId) + return false; + + const LLViewerObject* agentRoot = dynamic_cast(avatarPlayingMotionId->getRoot()); + if (!agentRoot) + return false; + + const LLUUID& assetIdTheyAreSittingOn = agentRoot->getID(); + if (assetIdTheyAreSittingOn == avatarPlayingMotionId->getID()) + return false; // they are not sitting on a thing + + LLViewerObject* object = gObjectList.findObject(assetIdTheyAreSittingOn); + if (!object || !object->permYouOwner()) + return false; // gAgent does not own what they are sitting on + + if (object->isInventoryPending()) + return false; + + if (object->isInventoryDirty() || !object->getInventoryRoot()) + { + object->requestInventory(); + return false; // whatever they are sitting on, we don't have the inventory list for yet + } + + LLInventoryItem* item = object->getInventoryItemByAsset(motionId, LLAssetType::AT_ANIMATION); + if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID()) + { + sMotionStatesOwnedByMe[motionId] = true; + return sMotionStatesOwnedByMe[motionId]; + } + + return false; +} + +bool FSPoseState::vector2IsSubsetOfVector1(std::vector newRecapture, std::vector oldRecapture) +{ + if (newRecapture.empty()) + return false; + if (oldRecapture.empty()) + return false; + + if (newRecapture.size() < oldRecapture.size()) + return false; + + for (S32 number : oldRecapture) + if (std::find(newRecapture.begin(), newRecapture.end(), number) == newRecapture.end()) + return false; + + return true; +} + +std::string FSPoseState::encodeVectorToString(const std::vector& vector) +{ + std::string encoded = ""; + if (vector.empty()) + return encoded; + + for (S32 numberToEncode : vector) + { + if (numberToEncode > 251) // max 216 at time of writing + continue; + + S32 number = numberToEncode; + + if (number >= 189) + { + encoded += "~"; + number -= 189; + } + + if (number >= 126) + { + encoded += "}"; + number -= 126; + } + + if (number >= 63) + { + encoded += "|"; + number -= 63; + } + + encoded += char(number + int('?')); + } + + return encoded; +} + +std::vector FSPoseState::decodeStringToVector(std::string_view vector) +{ + std::vector decoded; + if (vector.empty()) + return decoded; + + S32 number = 0; + for (char ch : vector) + { + if (ch > '~' || ch < '?') + continue; + + if (ch == '~') + { + number += 189; + continue; + } + + if (ch == '}') + { + number += 126; + continue; + } + + if (ch == '|') + { + number += 63; + continue; + } + + number -= int('?'); + number += S32(ch); + decoded.push_back(number); + number = 0; + } + + return decoded; +} diff --git a/indra/newview/fsposestate.h b/indra/newview/fsposestate.h index e941e28d70..df53bd396b 100644 --- a/indra/newview/fsposestate.h +++ b/indra/newview/fsposestate.h @@ -49,18 +49,7 @@ public: /// The avatar whose animations are to be captured. /// The posing motion. /// The names of the joints being recaptured. - void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured); - - /// - /// Add a new posing state, or updates the matching posing state with the supplied data. - /// - /// The avatar the posing state is intended for. - /// The ID of the animation. - /// The frame-time of the animation. - /// The names of the joints, if any, the animation should specifically be applied to. - /// The capture order. - /// True if the posing state was added or changed by the update data, otherwise false. - bool addOrUpdatePosingMotionState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder); + void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, const std::vector& jointNamesRecaptured); /// /// Removes all current animation states for the supplied avatar. @@ -72,16 +61,17 @@ public: /// Writes any documented poses for the supplied avatar to the supplied stream. /// /// The avatar whose animations may have been captured. + /// Whether to ignore ownership. For use when preparing saveRecord to send to another by collab. /// The record to add to. - void writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord); + void writeMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord); /// /// Restores pose state(s) from the supplied record. /// /// The avatar whose animations may have been captured. - /// The posing motion. + /// Whether to ignore ownership. For use when reading a local file. /// The record to read from. - void restoreMotionStates(LLVOAvatar* avatar, LLSD pose); + void restoreMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose); /// /// Applies the motion states for the supplied avatar to the supplied motion. @@ -122,20 +112,30 @@ private: bool motionApplied = false; /// - /// Whether the avatar owns the pose, or the pose was loaded. + /// For non-gAgent, we permit a query of the inventory of a prim they are sitting on. + /// Because this involves latency, we may retry ownership checking at save-time. /// - bool avatarOwnsPose = false; + bool requeriedAssetInventory = false; /// - /// When reloading, larger numbers are loaded last, nesting order and priority. - /// This is used to represent recaptures, where joints could be animated with different poses. + /// Whether gAgent owns the pose, or the pose was loaded from XML. /// - int captureOrder = 0; + bool gAgentOwnsPose = false; /// - /// When reloading, and if not-empty, the names of the bones this motionId should affect. + /// Represents 'capture layers: how the user layers animations 'on top of' others. /// - std ::string jointNamesAnimated; + S32 captureOrder = 0; + + /// + /// Represents in-layer order of capture. + /// + S32 inLayerOrder = 0; + + /// + /// When reloading, and if not-empty, the bone-numbers this motionId should affect. + /// + std ::vector jointNumbersAnimated; }; /// @@ -144,15 +144,61 @@ private: /// The avatar being posed by the motion. /// The posing motion. /// The order of the capture. - void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder); + void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, S32 captureOrder); /// - /// Gets whether the supplied avatar owns, and thus can save information about the supplied asset ID. + /// Gets whether gAgentID owns, and thus can save information about the supplied motionId. /// - /// The avatar to query ownership for. - /// The asset ID of the object. - /// True if the avatar owns the asset, otherwise false. - bool canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId); + /// The avatar playing the supplied motionId. + /// The motionId of the animation. + /// True if the gAgent owns the motionId, otherwise false. + /// + /// This only works reliably for self. + /// For motions playing on others, the motion needs to be an asset in gAgent's inventory. + /// + bool canSaveMotionId(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId); + + /// + /// Examines gAgent's animation source list for the supplied animation Id. + /// + /// The ID of the motion to query. + /// True if gAgent is playing the animation, otherwise false. + bool motionIdIsAgentAnimationSource(LLAssetID motionId); + + /// + /// Queries a specific condition of the supplied animation ID. + /// + /// The avatar to query for. + /// The motion ID to query for. + /// + /// True if the supplied avatar is sitting on an object owned by gAgent, and that object + /// contains an animation asset with the same assetId. + /// + /// + /// This is intended to test for a situation a photographer might arrange. + /// If you are sitting on photographer's prim, playing photographer's pose, and photographer wants to save their work, + /// this allows them to save the Animation ID and state to XML. + /// It is intended this be called twice at least, as it does not implement a callback onInventoryLoaded. + /// Presently this works fine: first time being when posing starts, second when pose is saved. + /// + bool motionIdIsFromPrimAgentOwnsAgentIsSittingOn(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId); + + /// + /// Tests if all the members of supplied vector2 are members of supplied vector1. + /// + /// The super-set. + /// The possible sub-set. + /// True if all members of vector2 are members of vector1, otherwise false. + bool vector2IsSubsetOfVector1(std::vector vector1, std::vector vector2); + + /// + /// Two symmetric methods for (de)serializing vectors to both XML and collab-safe short-as-possible strings and back again. + /// + /// + /// Collab-safe means ASCII-printable chars, and delimiter usage does not conflict with Collab's delimiter. + /// + std::string encodeVectorToString(const std::vector& vector); + std::vector decodeStringToVector(std::string_view vector); struct compareByCaptureOrder { @@ -160,13 +206,16 @@ private: { if (a.captureOrder < b.captureOrder) return true; // Ascending order + if (a.captureOrder == b.captureOrder && a.inLayerOrder < b.inLayerOrder) + return true; // Ascending order in layer return false; } }; - static std::map > sMotionStates; - static std::map sCaptureOrder; + static std::map> sMotionStates; + static std::map sCaptureOrder; + static std::map sMotionStatesOwnedByMe; }; #endif // LL_FSPoseState_H diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index 4d9a51b8b3..91f018061d 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -36,7 +36,7 @@ FSPosingMotion::FSPosingMotion(const LLUUID& id) : LLKeyframeMotion(id) mJointMotionList = &dummyMotionList; } -LLMotion::LLMotionInitStatus FSPosingMotion::onInitialize(LLCharacter *character) +LLMotion::LLMotionInitStatus FSPosingMotion::onInitialize(LLCharacter* character) { if (!character) return STATUS_FAILURE; @@ -184,7 +184,7 @@ void FSPosingMotion::removeJointFromState(LLJoint* joint) void FSPosingMotion::setJointState(LLJoint* joint, U32 state) { - if (mJointPoses.size() < 1) + if (mJointPoses.empty()) return; if (!joint) return; @@ -208,7 +208,7 @@ void FSPosingMotion::setJointState(LLJoint* joint, U32 state) FSJointPose* FSPosingMotion::getJointPoseByJointName(const std::string& name) { - if (mJointPoses.size() < 1) + if (name.empty() || mJointPoses.empty()) return nullptr; for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) @@ -222,15 +222,33 @@ FSJointPose* FSPosingMotion::getJointPoseByJointName(const std::string& name) return nullptr; } +FSJointPose* FSPosingMotion::getJointPoseByJointNumber(const S32 number) +{ + if (mJointPoses.empty()) + return nullptr; + if (number < 0) + return nullptr; + + for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) + { + if (poserJoint_iter->getJointNumber() != number) + continue; + + return &*poserJoint_iter; + } + + return nullptr; +} + bool FSPosingMotion::currentlyPosingJoint(LLJoint* joint) { - if (mJointPoses.size() < 1) + if (mJointPoses.empty()) return false; if (!joint) return false; - LLPose* pose = this->getPose(); + LLPose* pose = getPose(); if (!pose) return false; @@ -270,14 +288,14 @@ void FSPosingMotion::setJointBvhLock(FSJointPose* joint, bool lockInBvh) joint->zeroBaseRotation(lockInBvh); } -bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames) +bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, const std::vector& selectedJointNumbers) { FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast(motionToLoad); if (!motionToLoadAsFsPosingMotion) return false; LLJoint::JointPriority priority = motionToLoad->getPriority(); - bool motionIsForAllJoints = selectedJointNames.empty(); + bool motionIsForAllJoints = selectedJointNumbers.empty(); LLQuaternion rot; LLVector3 position, scale; @@ -285,16 +303,18 @@ bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionT for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) { - std::string jointName = poserJoint_iter->jointName(); + S32 jointNumber = poserJoint_iter->getJointNumber(); + std::string jointName = poserJoint_iter->jointName(); - bool motionIsForThisJoint = selectedJointNames.find(jointName) != std::string::npos; + bool motionIsForThisJoint = + std::find(selectedJointNumbers.begin(), selectedJointNumbers.end(), jointNumber) != selectedJointNumbers.end(); if (!motionIsForAllJoints && !motionIsForThisJoint) continue; hasRotation = hasPosition = hasScale = false; motionToLoadAsFsPosingMotion->getJointStateAtTime(jointName, timeToLoadAt, &hasRotation, &rot, &hasPosition, &position, &hasScale, &scale); - if (hasRotation) + if (hasRotation && !poserJoint_iter->userHasSetBaseRotationToZero()) poserJoint_iter->setBaseRotation(rot, priority); if (hasPosition) @@ -337,16 +357,17 @@ void FSPosingMotion::getJointStateAtTime(std::string jointPoseName, F32 timeToLo } } -bool FSPosingMotion::otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames) +bool FSPosingMotion::otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, const std::vector& recapturedJointNumbers) { FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast(motionToQuery); if (!motionToLoadAsFsPosingMotion) return false; - return motionToLoadAsFsPosingMotion->motionAnimatesJoints(recapturedJointNames); + return motionToLoadAsFsPosingMotion->motionAnimatesJoints(recapturedJointNumbers); } -bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames) +// Do not try to access FSPosingMotion state; you are a LLKeyframeMotion cast as a FSPosingMotion, NOT an FSPosingMotion. +bool FSPosingMotion::motionAnimatesJoints(const std::vector& recapturedJointNumbers) { if (mJointMotionList == nullptr) return false; @@ -354,7 +375,9 @@ bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames) for (U32 i = 0; i < mJointMotionList->getNumJointMotions(); i++) { JointMotion* jm = mJointMotionList->getJointMotion(i); - if (recapturedJointNames.find(jm->mJointName) == std::string::npos) + LLJoint* joint = mCharacter->getJoint(jm->mJointName); + + if (std::find(recapturedJointNumbers.begin(), recapturedJointNumbers.end(), joint->getJointNum()) == recapturedJointNumbers.end()) continue; if (jm->mRotationCurve.mNumKeys > 0) @@ -364,20 +387,19 @@ bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames) return false; } -void FSPosingMotion::resetBonePriority(std::string boneNamesToReset) +void FSPosingMotion::resetBonePriority(const std::vector& boneNumbersToReset) { - if (boneNamesToReset.empty()) - return; - - for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) + for (S32 boneNumber : boneNumbersToReset) { - std::string jointName = poserJoint_iter->jointName(); - if (boneNamesToReset.find(jointName) != std::string::npos) - poserJoint_iter->setJointPriority(LLJoint::LOW_PRIORITY); + for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) + { + if (poserJoint_iter->getJointNumber() == boneNumber) + poserJoint_iter->setJointPriority(LLJoint::LOW_PRIORITY); + } } } -bool FSPosingMotion::vectorsNotQuiteEqual(LLVector3 v1, LLVector3 v2) const +bool FSPosingMotion::vectorsNotQuiteEqual(const LLVector3& v1, const LLVector3& v2) const { if (vectorAxesAlmostEqual(v1.mV[VX], v2.mV[VX]) && vectorAxesAlmostEqual(v1.mV[VY], v2.mV[VY]) && diff --git a/indra/newview/fsposingmotion.h b/indra/newview/fsposingmotion.h index 0f336b3ba8..2193973cff 100644 --- a/indra/newview/fsposingmotion.h +++ b/indra/newview/fsposingmotion.h @@ -68,7 +68,7 @@ public: // run-time (post constructor) initialization, // called after parameters have been set // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual LLMotionInitStatus onInitialize(LLCharacter* character); // called when a motion is activated // must return TRUE to indicate success, or else @@ -108,6 +108,13 @@ public: /// The matching joint pose, if found, otherwise null. FSJointPose* getJointPoseByJointName(const std::string& name); + /// + /// Gets the joint pose by its LLJoint number. + /// + /// The number of the joint to get the pose for. + /// The matching joint pose, if found, otherwise null. + FSJointPose* getJointPoseByJointNumber(const S32 number); + /// /// Gets the motion identity for this animation. /// @@ -139,9 +146,9 @@ public: /// /// The motion whose joint rotations (etc) we want to copy to this. /// The play-time the animation should be advanced to derive the correct joint state. - /// If only some of the joints should be animated by this motion, name them here. + /// If only some of the joints should be animated by this motion, number them here. /// - bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames); + bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, const std::vector& selectedJointNumbers); /// /// Tries to get the rotation, position and scale for the supplied joint name at the supplied time. @@ -164,27 +171,27 @@ public: /// /// Resets the bone priority to zero for the joints named in the supplied string. /// - /// The string containg bone names (like mPelvis). - void resetBonePriority(std::string boneNamesToReset); + /// The vector containing bone numbers. + void resetBonePriority(const std::vector& boneNumbersToReset); /// /// Queries whether the supplied motion animates any of the joints named in the supplied string. /// /// The motion to query. - /// A string containing all of the joint names. + /// A string containing all of the joint numbers. /// True if the motion animates any of the bones named, otherwise false. - bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames); + bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, const std::vector& recapturedJointNumbers); /// /// Queries whether the this motion animates any of the joints named in the supplied string. /// - /// A string containing all of the joint names. + /// A vector containing all of the joint numbers this motion animates. /// True if the motion animates any of the bones named, otherwise false. /// /// The most significant thing this method does is provide access to protected properties of an LLPosingMotion. /// Thus its most common usage would be to access those properties for an arbitrary animation. /// - bool motionAnimatesJoints(std::string recapturedJointNames); + bool motionAnimatesJoints(const std::vector& recapturedJointNumbers); private: /// @@ -267,7 +274,7 @@ private: /// The first vector to compare. /// The sceond vector to compare. /// true if the vectors are "close enough", otherwise false. - bool vectorsNotQuiteEqual(LLVector3 v1, LLVector3 v2) const; + bool vectorsNotQuiteEqual(const LLVector3& v1, const LLVector3& v2) const; /// /// Determines if two quaternions are near enough to equal. diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 509fa4b6e4..1dff60a48c 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -292,10 +292,10 @@ using namespace LL; #include "nd/ndetw.h" // Windows Event Tracing, does nothing on OSX/Linux. #include "nd/ndlogthrottle.h" +#include "aoengine.h" #include "fsradar.h" #include "fsassetblacklist.h" #include "bugsplatattributes.h" -// #include "fstelemetry.h" // Tracy profiler support #if LL_LINUX && LL_GTK #include "glib.h" @@ -2271,6 +2271,8 @@ bool LLAppViewer::cleanup() LLEnvironment::getInstance()->saveToSettings(); } + AOEngine::deleteSingleton(); + // Must do this after all panels have been deleted because panels that have persistent rects // save their rects on delete. if(mSaveSettingsOnExit) // Backup Settings diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index fb781a635d..23cb507ec1 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -191,8 +191,8 @@ LLAvatarList::LLAvatarList(const Params& p) , mRlvCheckShowNames(false) // [/RLVa:KB] , mShowVoiceVolume(p.show_voice_volume) -, mShowUsername((bool)gSavedSettings.getBOOL("NameTagShowUsernames")) -, mShowDisplayName((bool)gSavedSettings.getBOOL("UseDisplayNames")) +, mShowUsername(gSavedSettings.getBOOL("NameTagShowUsernames")) +, mShowDisplayName(gSavedSettings.getBOOL("UseDisplayNames")) { setCommitOnSelectionChange(true); @@ -219,13 +219,13 @@ LLAvatarList::LLAvatarList(const Params& p) void LLAvatarList::handleDisplayNamesOptionChanged() { // FIRE-1089: Set the proper name options for the AvatarListItem before we update the list. - mShowUsername = (bool)gSavedSettings.getBOOL("NameTagShowUsernames"); - mShowDisplayName = (bool)gSavedSettings.getBOOL("UseDisplayNames"); + mShowUsername = gSavedSettings.getBOOL("NameTagShowUsernames"); + mShowDisplayName = gSavedSettings.getBOOL("UseDisplayNames"); std::vector items; getItems(items); - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + for (auto panel : items) { - LLAvatarListItem* item = static_cast(*it); + LLAvatarListItem* item = static_cast(panel); item->showUsername(mShowUsername, false); item->showDisplayName(mShowDisplayName, false); } @@ -265,9 +265,9 @@ void LLAvatarList::updateRlvRestrictions(ERlvBehaviour behavior, ERlvParamType t { std::vector items; getItems(items); - for (std::vector::const_iterator it = items.begin(); it != items.end(); it++) + for (auto panel : items) { - LLAvatarListItem* item = static_cast(*it); + LLAvatarListItem* item = static_cast(panel); item->updateRlvRestrictions(); } } @@ -559,11 +559,11 @@ S32 LLAvatarList::notifyParent(const LLSD& info) return 1; } // [SL:KB] - Patch: UI-AvatarListDndShare | Checked: 2011-06-19 (Catznip-2.6.0c) | Added: Catznip-2.6.0c - else if ( (info.has("select")) && (info["select"].isUUID()) ) + else if (info.has("select") && info["select"].isUUID()) { const LLSD& sdValue = getSelectedValue(); const LLUUID idItem = info["select"].asUUID(); - if ( (!sdValue.isDefined()) || ((sdValue.isUUID()) && (sdValue.asUUID() != idItem)) ) + if (!sdValue.isDefined() || (sdValue.isUUID() && sdValue.asUUID() != idItem)) { resetSelection(); selectItemByUUID(info["select"].asUUID()); @@ -731,18 +731,18 @@ void LLAvatarList::onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) // static std::string LLAvatarList::getNameForDisplay(const LLUUID& avatar_id, const LLAvatarName& av_name, bool show_displayname, bool show_username, bool force_use_complete_name, bool rlv_check_shownames) { - bool fRlvCanShowName = (!rlv_check_shownames) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, avatar_id)); + const bool fRlvCanShowName = (!rlv_check_shownames) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, avatar_id)); if (show_displayname && !show_username) { - return ( (fRlvCanShowName) ? av_name.getDisplayName() : RlvStrings::getAnonym(av_name) ); + return (fRlvCanShowName ? av_name.getDisplayName() : RlvStrings::getAnonym(av_name)); } else if (!show_displayname && show_username) { - return ( (fRlvCanShowName) ? av_name.getUserName() : RlvStrings::getAnonym(av_name) ); + return (fRlvCanShowName ? av_name.getUserName() : RlvStrings::getAnonym(av_name)); } else { - return ( (fRlvCanShowName) ? av_name.getCompleteName(false, force_use_complete_name) : RlvStrings::getAnonym(av_name) ); + return (fRlvCanShowName ? av_name.getCompleteName(true, force_use_complete_name) : RlvStrings::getAnonym(av_name)); } } // diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index bad15b3333..9920f14a48 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -368,7 +368,12 @@ void LLFloaterModelPreview::reshape(S32 width, S32 height, bool called_from_pare { LLFloaterModelUploadBase::reshape(width, height, called_from_parent); - LLView* preview_panel = getChild("preview_panel"); + // This can get called before the floater is actually built + //LLView* preview_panel = getChild("preview_panel"); + LLView* preview_panel = findChild("preview_panel"); + if (!preview_panel) + return; + // LLRect rect = preview_panel->getRect(); if (rect != mPreviewRect) diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 2d0780d2b9..7bcbcabdee 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -1129,7 +1129,7 @@ void LLOutfitListBase::refreshList(const LLUUID& category_id) // FIRE-6958/VWR-2862; Handle large amounts of outfits, write a least a warning into the logs. S32 currentOutfitsAmount = (S32)mRefreshListState.Added.size(); - constexpr S32 maxSuggestedOutfits = 200; + constexpr S32 maxSuggestedOutfits = 1000; if (currentOutfitsAmount > maxSuggestedOutfits) { LL_WARNS() << "Large amount of outfits found: " << currentOutfitsAmount << " this may cause hangs and disconnects" << LL_ENDL; diff --git a/indra/newview/lltoolcomp.h b/indra/newview/lltoolcomp.h index fbce6f9079..04364b45e4 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -272,7 +272,8 @@ public: virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; virtual void render() override; void setAvatar(LLVOAvatar* avatar) { mManip->setAvatar(avatar); }; - void setJoint( LLJoint * joint ) { mManip->setJoint( joint ); }; + void setJoint(LLJoint* joint) { mManip->setJoint(joint); }; + void setReferenceFrame(E_PoserReferenceFrame frame) { mManip->setReferenceFrame(frame); }; // Optional override if you have SHIFT/CTRL combos virtual LLTool* getOverrideTool(MASK mask) override; diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 3a2031dd61..8c37f6cfb2 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -913,7 +913,27 @@ void handleUsernameFormatOptionChanged(const LLSD& newvalue) // Global online status toggle void handleGlobalOnlineStatusChanged(const LLSD& newvalue) { + if (gSavedPerAccountSettings.getBOOL("GlobalOnlineStatusCurrentlyReverting")) + { + gSavedPerAccountSettings.setBOOL("GlobalOnlineStatusCurrentlyReverting", false); + return; + } bool visible = newvalue.asBoolean(); + LLSD payload; + payload["visible"] = visible; + LLNotificationsUtil::add("ConfirmGlobalOnlineStatusToggle", LLSD(), payload, applyGlobalOnlineStatusChange); +} + +void applyGlobalOnlineStatusChange(const LLSD& notification, const LLSD& response) +{ + bool visible = notification["payload"]["visible"].asBoolean(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + gSavedPerAccountSettings.setBOOL("GlobalOnlineStatusCurrentlyReverting", true); + gSavedPerAccountSettings.setBOOL("GlobalOnlineStatusToggle", !visible); + return; + } LLAvatarTracker::buddy_map_t all_buddies; LLAvatarTracker::instance().copyBuddyList(all_buddies); diff --git a/indra/newview/llviewercontrol.h b/indra/newview/llviewercontrol.h index e847080e04..6f6a231b67 100644 --- a/indra/newview/llviewercontrol.h +++ b/indra/newview/llviewercontrol.h @@ -61,4 +61,8 @@ extern LLControlGroup gCrashSettings; // Set after settings loaded extern std::string gLastRunVersion; +// Global online status toggle +void applyGlobalOnlineStatusChange(const LLSD& notification, const LLSD& response); +// + #endif // LL_LLVIEWERCONTROL_H diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index e4a7c1398c..c07019f4af 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -216,6 +216,7 @@ #include "fsfloaterprotectedfolders.h" #include "fsfloaterradar.h" #include "fsfloatersearch.h" +#include "fsfloatersplashscreensettings.h" #include "fsfloaterstatistics.h" #include "fsfloaterstreamtitle.h" #include "fsfloaterteleporthistory.h" @@ -654,6 +655,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("fs_poser", "floater_fs_poser.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // [FIRE-30873]: Poser LLFloaterReg::add("fs_protectedfolders", "floater_fs_protectedfolders.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("fs_radar", "floater_fs_radar.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("fs_splash_screen_settings", "floater_fs_splash_screen_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("fs_streamtitle", "floater_fs_streamtitle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("fs_streamtitlehistory", "floater_fs_streamtitlehistory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("fs_teleporthistory", "floater_fs_teleporthistory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 70f49decb7..1354b3e554 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -2296,12 +2296,50 @@ void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const L else if ("lsl" == type_name) { const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); + + // FIRE-36164 Apply the custom "New Script" setting for inventory scripts + bool custom_script_inserted = false; + if (gSavedPerAccountSettings.getBOOL("FSBuildPrefs_UseCustomScript")) + { + if (LLUUID custom_script_id(gSavedPerAccountSettings.getString("FSBuildPrefs_CustomScriptItem")); custom_script_id.notNull()) + { + if (auto custom_script = gInventory.getItem(custom_script_id); custom_script && custom_script->getType() == LLAssetType::AT_LSL_TEXT) + { + LLPointer cb = NULL; + if (created_cb != NULL) + { + cb = new LLBoostFuncInventoryCallback(create_script_cb); + cb->addOnFireFunc(created_cb); + } + else + { + cb = new LLBoostFuncInventoryCallback(create_script_cb); + } + copy_inventory_item( + gAgent.getID(), + custom_script->getPermissions().getOwner(), + custom_script_id, + parent_id, + NEW_LSL_NAME, + cb); + custom_script_inserted = true; + } + } + } + if (!custom_script_inserted) + { + // + create_new_item(NEW_LSL_NAME, parent_id, LLAssetType::AT_LSL_TEXT, LLInventoryType::IT_LSL, PERM_MOVE | PERM_TRANSFER, created_cb); // overridden in create_new_item + + // + } + // } else if ("notecard" == type_name) { diff --git a/indra/newview/skins/default/xui/az/floater_fs_splash_screen_settings.xml b/indra/newview/skins/default/xui/az/floater_fs_splash_screen_settings.xml new file mode 100644 index 0000000000..ede5880759 --- /dev/null +++ b/indra/newview/skins/default/xui/az/floater_fs_splash_screen_settings.xml @@ -0,0 +1,20 @@ + + + + Giriş ekranının göstərilmə seçimlərini burada tənzimləyin. Bu seçimlər baxış proqramını növbəti dəfə yenidən başlatdığınız zaman qüvvəyə minəcək. + + + Bölmənin Görünürlüyü: + + + + + + Əlçatanlıq Seçimləri: + + + + + + + diff --git a/indra/newview/skins/default/xui/az/floater_model_preview.xml b/indra/newview/skins/default/xui/az/floater_model_preview.xml index 786b5aa416..95ae5bc775 100644 --- a/indra/newview/skins/default/xui/az/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/az/floater_model_preview.xml @@ -259,7 +259,7 @@ name="mesh_preview_degenerate_edge_color"/> + name="mesh_preview_degenerate_fill_color"/> diff --git a/indra/newview/skins/default/xui/de/floater_fs_poser.xml b/indra/newview/skins/default/xui/de/floater_fs_poser.xml index 4ffe3de5fc..5692aaac65 100644 --- a/indra/newview/skins/default/xui/de/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/de/floater_fs_poser.xml @@ -186,7 +186,7 @@ - + Hoch/Runter: @@ -245,7 +245,7 @@ @@ -1623,34 +1720,112 @@ width="430"> tool_tip="Copy changes to the opposite joint." left_pad="1" top_delta="0" - width="43" > + width="53"> + + + diff --git a/indra/newview/skins/default/xui/en/floater_fs_splash_screen_settings.xml b/indra/newview/skins/default/xui/en/floater_fs_splash_screen_settings.xml new file mode 100644 index 0000000000..810e56ebb1 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_fs_splash_screen_settings.xml @@ -0,0 +1,132 @@ + + + + + Configure splash screen display options here. These settings will take effect after the next viewer restart. + + + + Section Visibility: + + + + + + + + + + Accessibility Options: + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index e8cecbc739..f7c90b5954 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1559,7 +1559,6 @@ height="306" layout="topleft" left="3" - ignore_tab="false" right="-21" name="log_tab_border" top_pad="0" @@ -1605,7 +1604,6 @@ height="306" layout="topleft" left="3" - ignore_tab="false" right="-21" name="mesh_preview_settings_tab_border" top_pad="0" @@ -1952,7 +1950,7 @@ can_apply_immediately="true" label="Bad Triangle Fill" tool_tip="Fill color for degenerate (thin) triangles in preview window on the Mesh uploader" - name="mesh_degenerate_fill_color"/> + name="mesh_preview_degenerate_fill_color"/> + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml b/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml index 6451ccb2c5..8dd6be75f9 100644 --- a/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml +++ b/indra/newview/skins/default/xui/en/menu_fs_contacts_friends.xml @@ -115,6 +115,15 @@ + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_url_agent.xml b/indra/newview/skins/default/xui/en/menu_url_agent.xml index 4dfe325405..ff41e69414 100644 --- a/indra/newview/skins/default/xui/en/menu_url_agent.xml +++ b/indra/newview/skins/default/xui/en/menu_url_agent.xml @@ -177,4 +177,23 @@ + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 6a570690a0..24c46de31f 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -14144,6 +14144,19 @@ the region "[REGION]"? name="okbutton" yestext="OK"/> + +Are you sure you want to change your online status visibility for all friends at once? + +Due to server load, this mass change can take a while to become effective and may cause temporary issues for some friends seeing your online status. + confirm + + - A large number of outfits were detected: [AMOUNT]. This may cause viewer hangs or disconnects. Consider reducing the number of outfits for better performance (below [MAX]). + A large number of outfits were detected: [AMOUNT]. This may cause viewer hangs or disconnects. Consider reducing the number of outfits for better performance (below [MAX]). THIS IS ONLY A SUGGESTION - if your computer is functioning normally, you can safely ignore it. Too many entries. The CSV file has [COUNT] entries and there are [MAX] slots available. Successfully processed [COUNT] entries. + + Flood + Surface Only + Raycast + Very High + Ultra + Maximum + diff --git a/indra/newview/skins/default/xui/es/floater_fs_splash_screen_settings.xml b/indra/newview/skins/default/xui/es/floater_fs_splash_screen_settings.xml new file mode 100644 index 0000000000..693a66d09c --- /dev/null +++ b/indra/newview/skins/default/xui/es/floater_fs_splash_screen_settings.xml @@ -0,0 +1,20 @@ + + + + Configure aquí las opciones de visualización de la pantalla de inicio. Estos ajustes tendrán efecto después del próximo reinicio del visor. + + + Visibilidad de la sección: + + + + + + Opciones de Accesibilidad: + + + + + + + diff --git a/indra/newview/skins/default/xui/fr/floater_fs_poser.xml b/indra/newview/skins/default/xui/fr/floater_fs_poser.xml index 8cc38cf212..c3cd8bcd6c 100644 --- a/indra/newview/skins/default/xui/fr/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/fr/floater_fs_poser.xml @@ -181,7 +181,7 @@ - + Haut/bas : @@ -248,7 +248,7 @@ - + diff --git a/indra/newview/skins/default/xui/fr/menu_avatar_other.xml b/indra/newview/skins/default/xui/fr/menu_avatar_other.xml index 11e03193c4..ca8ab6128d 100644 --- a/indra/newview/skins/default/xui/fr/menu_avatar_other.xml +++ b/indra/newview/skins/default/xui/fr/menu_avatar_other.xml @@ -9,6 +9,7 @@ + diff --git a/indra/newview/skins/default/xui/fr/menu_conversation.xml b/indra/newview/skins/default/xui/fr/menu_conversation.xml index 3d5785e9bf..ac3cff20d3 100644 --- a/indra/newview/skins/default/xui/fr/menu_conversation.xml +++ b/indra/newview/skins/default/xui/fr/menu_conversation.xml @@ -25,7 +25,7 @@ - + diff --git a/indra/newview/skins/default/xui/fr/menu_fs_contacts_friends.xml b/indra/newview/skins/default/xui/fr/menu_fs_contacts_friends.xml index 75d91d238d..6d42fb5016 100644 --- a/indra/newview/skins/default/xui/fr/menu_fs_contacts_friends.xml +++ b/indra/newview/skins/default/xui/fr/menu_fs_contacts_friends.xml @@ -12,7 +12,8 @@ - + + diff --git a/indra/newview/skins/default/xui/fr/menu_media_ctrl.xml b/indra/newview/skins/default/xui/fr/menu_media_ctrl.xml index f86d41e0ab..7ae7c38a4b 100644 --- a/indra/newview/skins/default/xui/fr/menu_media_ctrl.xml +++ b/indra/newview/skins/default/xui/fr/menu_media_ctrl.xml @@ -4,4 +4,5 @@ + diff --git a/indra/newview/skins/default/xui/fr/menu_object.xml b/indra/newview/skins/default/xui/fr/menu_object.xml index 8e650e5b6c..1cfbf9597a 100644 --- a/indra/newview/skins/default/xui/fr/menu_object.xml +++ b/indra/newview/skins/default/xui/fr/menu_object.xml @@ -2,6 +2,7 @@ + @@ -21,7 +22,7 @@ - + diff --git a/indra/newview/skins/default/xui/fr/menu_object_icon.xml b/indra/newview/skins/default/xui/fr/menu_object_icon.xml index 97f78949a0..19498fab66 100644 --- a/indra/newview/skins/default/xui/fr/menu_object_icon.xml +++ b/indra/newview/skins/default/xui/fr/menu_object_icon.xml @@ -3,6 +3,7 @@ + diff --git a/indra/newview/skins/default/xui/fr/menu_participant_list.xml b/indra/newview/skins/default/xui/fr/menu_participant_list.xml index 4a2df63059..f3def29811 100644 --- a/indra/newview/skins/default/xui/fr/menu_participant_list.xml +++ b/indra/newview/skins/default/xui/fr/menu_participant_list.xml @@ -11,15 +11,18 @@ - + - + + + + diff --git a/indra/newview/skins/default/xui/fr/menu_url_agent.xml b/indra/newview/skins/default/xui/fr/menu_url_agent.xml index b006db24e0..e19edaac6d 100644 --- a/indra/newview/skins/default/xui/fr/menu_url_agent.xml +++ b/indra/newview/skins/default/xui/fr/menu_url_agent.xml @@ -13,6 +13,14 @@ + + + + + + - + + + diff --git a/indra/newview/skins/default/xui/fr/menu_url_objectim.xml b/indra/newview/skins/default/xui/fr/menu_url_objectim.xml index 8a320b27ff..acd4c26b65 100644 --- a/indra/newview/skins/default/xui/fr/menu_url_objectim.xml +++ b/indra/newview/skins/default/xui/fr/menu_url_objectim.xml @@ -3,6 +3,7 @@ + diff --git a/indra/newview/skins/default/xui/fr/notifications.xml b/indra/newview/skins/default/xui/fr/notifications.xml index 3f8337ef38..ff11211a19 100644 --- a/indra/newview/skins/default/xui/fr/notifications.xml +++ b/indra/newview/skins/default/xui/fr/notifications.xml @@ -882,6 +882,15 @@ Assurez-vous que le fichier a l'extension correcte. Impossible de lire le fichier son chargé : [FILE] + +Le chargement de modèles n'est pas encore disponible sur Apple Silicon, mais sera pris en charge dans une prochaine version. + +Solution de contournement : cliquez avec le bouton droit sur l'application [APP_NAME] dans le Finder, sélectionnez +« Obtenir des informations », puis cochez « Ouvrir avec Rosetta ». + + + La bibliothèque physique n'est pas présente, certaines fonctionnalités du programme de chargement de modèles peuvent ne pas fonctionner ou ne pas fonctionner correctement. + Il semble que le fichier ne soit pas un fichier RIFF WAVE : [FILE] @@ -5187,6 +5196,10 @@ Ils peuvent utiliser http://opensimulator.org/wiki/inventory pour résoudre les Impossible de déplacer les fichiers. Chemin précédent rétabli. + + Votre système dispose de [TOTAL_MEM] Mo de mémoire, ce qui peut s'avérer insuffisant pour exécuter la visionneuse avec des paramètres élevés et risque d'entraîner des problèmes. + + Problème lors de l'enregistrement des droits d'objet par défaut : [REASON]. Réessayez de définir les droits par défaut ultérieurement. @@ -5404,6 +5417,12 @@ la région "[REGION]" ? Impossible de créer un autre favori car le nombre maximum de favoris a déjà été créé. + +Êtes-vous sûr de vouloir modifier la visibilité de votre statut en ligne pour tous vos amis à la fois ? + +En raison de la charge du serveur, cette modification en masse peut prendre un certain temps avant de prendre effet et peut entraîner des problèmes temporaires pour certains amis qui voient votre statut en ligne. + + En raison de la charge du serveur, le basculement massif de la visibilité du statut en ligne peut prendre un certain temps avant d'être effectif. Veuillez être patient. @@ -5655,6 +5674,10 @@ https://wiki.firestormviewer.org/antivirus_whitelisting Remplacer la pose “[POSE_NAME]”? + + Un grand nombre de tenues ont été détectées : [AMOUNT]. Cela peut entraîner des blocages ou des déconnexions de la visionneuse. Envisagez de réduire le nombre de tenues pour améliorer les performances (en dessous de [MAX]). IL S'AGIT UNIQUEMENT D'UNE SUGGESTION. Si votre ordinateur fonctionne normalement, vous pouvez ignorer cette recommandation. + + Demande de connexion refusée par Primfeed. diff --git a/indra/newview/skins/default/xui/fr/panel_people.xml b/indra/newview/skins/default/xui/fr/panel_people.xml index 9c6bbbb254..4c79b5389b 100644 --- a/indra/newview/skins/default/xui/fr/panel_people.xml +++ b/indra/newview/skins/default/xui/fr/panel_people.xml @@ -47,6 +47,7 @@ Pour rechercher des résidents avec qui passer du temps, utilisez [secondlife://