# Conflicts:
#	indra/newview/pipeline.cpp
master
Ansariel 2025-10-11 14:35:54 +02:00
commit 98a48cf1c1
47 changed files with 1427 additions and 138 deletions

View File

@ -90,7 +90,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Checkout build variables

View File

@ -73,7 +73,7 @@ jobs:
- name: Set up Python (normal case)
if: matrix.container_image != 'ubuntu:22.04'
id: py311_setup
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.11'
check-latest: true

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check PR description
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const description = context.payload.pull_request.body || '';

View File

@ -9,7 +9,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
- uses: actions/labeler@v6
with:
configuration-path: .github/labeler.yaml
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
- uses: pre-commit/action@v3.0.1

View File

@ -12,7 +12,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
id: stale
with:
stale-pr-message: This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or it will be closed in 7 days

View File

@ -35,7 +35,7 @@ jobs:
echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV}
echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV}
- name: Update Tag
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8
with:
# use a real access token instead of GITHUB_TOKEN default.
# required so that the results of this tag creation can trigger the build workflow

View File

@ -328,7 +328,11 @@ void LLDiskCache::purge()
const std::string LLDiskCache::metaDataToFilepath(const LLUUID& id, LLAssetType::EType at)
{
return llformat("%s%s%s_%s_0.asset", sCacheDir.c_str(), gDirUtilp->getDirDelimiter().c_str(), CACHE_FILENAME_PREFIX.c_str(), id.asString().c_str());
// <FS:Ansariel> Store assets in subfolders
//return llformat("%s%s%s_%s_0.asset", sCacheDir.c_str(), gDirUtilp->getDirDelimiter().c_str(), CACHE_FILENAME_PREFIX.c_str(), id.asString().c_str());
char id_string[36]{};
return llformat("%s%s%c%s%s_%s_0.asset", sCacheDir.c_str(), gDirUtilp->getDirDelimiter().c_str(), id.toStringFast(id_string)[0], gDirUtilp->getDirDelimiter().c_str(), CACHE_FILENAME_PREFIX.c_str(), id.asString().c_str());
// <FS:Ansariel>
}
const std::string LLDiskCache::getCacheInfo()

View File

@ -276,29 +276,111 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r
// Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl().
constexpr S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support
constexpr S32 max_components = 4; // assumed the file has four components; three color and alpha
// Use MAX_IMAGE_SIZE_DEFAULT (currently 2048) if either dimension is unknown (zero)
S32 width = (w > 0) ? w : 2048;
S32 height = (h > 0) ? h : 2048;
S32 max_dimension = llmax(width, height); // Find largest dimension
S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64)
S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4); // Find number of powers of two between extents and block size to a minimum of 4
block_area *= llmax(max_layers, 1); // Adjust initial block area by max number of layers
S32 totalbytes = (S32) (MIN_LAYER_SIZE * max_components * precision); // Start estimation with a minimum reasonable size
S32 block_layers = 0;
while (block_layers <= max_layers) // Walk the layers
// <FS:Beq> [FIRE-35987] slow textures due to overfetch in j2c header size estimation
// Preserve recent LL body for reference (see PRs #2406, #2525, #4018, #4020):
// // Use MAX_IMAGE_SIZE_DEFAULT (currently 2048) if either dimension is unknown (zero)
// S32 width = (w > 0) ? w : 2048;
// S32 height = (h > 0) ? h : 2048;
// S32 max_dimension = llmax(width, height); // Find largest dimension
// S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64)
// S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4); // Find number of powers of two between extents and block size to a minimum of 4
// block_area *= llmax(max_layers, 1); // Adjust initial block area by max number of layers
// S32 totalbytes = (S32) (MIN_LAYER_SIZE * max_components * precision); // Start estimation with a minimum reasonable size
// S32 block_layers = 0;
// while (block_layers <= max_layers) // Walk the layers
// {
// if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer.
// totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate
// block_layers++; // Move to next layer
// block_area *= 4; // Increase block area by power of four
// }
// totalbytes /= 8; // to bytes
// totalbytes += calcHeaderSizeJ2C(); // header
//
// return totalbytes;
// --- Use 7.1.11 basis with fixes implied by LL PRs ---
(void)comp; // retained for parity with the viewer signature
const S32 hard_cap = 12; // sanity cap
const S64 base_layer_area = static_cast<S64>(MAX_BLOCK_SIZE) * static_cast<S64>(MAX_BLOCK_SIZE); // 64x64 blocks at discard 5
const S32 discard_layers = std::max(5 - discard_level, 0);
const double rate64 = static_cast<double>(rate);
const S64 header_bytes = static_cast<S64>(calcHeaderSizeJ2C());
const S64 bits_per_tile = static_cast<S64>(max_components) * static_cast<S64>(precision);
// helper lambda: layer area to estimated bit budget
auto scaled_bits = [rate64, bits_per_tile](S64 layer_area) -> S64
{
if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer.
totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate
block_layers++; // Move to next layer
block_area *= 4; // Increase block area by power of four
const S64 layer_bits = layer_area * bits_per_tile;
return static_cast<S64>(std::llround(static_cast<double>(layer_bits) * rate64));
};
// If dimensions are unknown, provide a *reliable discard-5 estimate* without assuming a 2k texture.
// This lets the fetcher skip a header pass for d5 while avoiding the large overfetch seen previously.
S64 total_bits = 0;
if (w <= 0 || h <= 0)
{
total_bits = scaled_bits(base_layer_area);
}
else
{
// Classic surface walk: start at 64x64 and grow by 4x until we cover the surface.
const S64 surface = static_cast<S64>(w) * static_cast<S64>(h);
totalbytes /= 8; // to bytes
totalbytes += calcHeaderSizeJ2C(); // header
S64 layer_area = base_layer_area;
S32 nb_layers = 1;
return totalbytes;
// First layer (7.1.11 did first-term outside loop). Keep it explicit for clarity.
total_bits = scaled_bits(layer_area);
while (surface > layer_area && nb_layers < hard_cap)
{
if (nb_layers <= discard_layers)
{
total_bits += scaled_bits(layer_area);
}
++nb_layers;
layer_area *= 4;
}
// Allow extra layers for large (2k+) assets uploaded with 78 layers (from LL change justification)
// If the max dimension implies more pyramid steps than the surface loop used, extend the walk up to that.
// Note: this mostly affect long thin textures like 2048x256 as best I can tell.
{
const S32 max_dimension = std::max(w, h);
const float ratio = (max_dimension > 0)
? static_cast<float>(max_dimension) / static_cast<float>(MAX_BLOCK_SIZE)
: 0.0f;
const S32 dimension_layers = (ratio > 0.0f)
? std::max(static_cast<S32>(std::floor(std::log2(ratio))) + 1, 1)
: 1;
if (dimension_layers > nb_layers)
{
S32 extra = std::min(dimension_layers, hard_cap) - nb_layers;
while (extra-- > 0)
{
if (nb_layers <= discard_layers)
{
total_bits += scaled_bits(layer_area);
}
++nb_layers;
layer_area *= 4;
}
}
}
}
// Convert to bytes and add header.
S64 est = total_bits / 8;
est += header_bytes;
est = llclamp(est, (S64)0, (S64)std::numeric_limits<S32>::max());
return static_cast<S32>(est);
// </FS:Beq>
}
S32 LLImageJ2C::calcHeaderSize()
{
return calcHeaderSizeJ2C();

View File

@ -774,6 +774,7 @@ void LLPermissions::importLLSD(const LLSD& sd_perm)
}
}
fixOwnership();
fix();
}

View File

@ -642,6 +642,11 @@ bool LLImageGL::setSize(S32 width, S32 height, S32 ncomponents, S32 discard_leve
if(discard_level > 0)
{
mMaxDiscardLevel = llmax(mMaxDiscardLevel, (S8)discard_level);
// <FS:minerjr> [FIRE-35361] RenderMaxTextureResolution caps texture resolution lower than intended
// 2K textures could set the mMaxDiscardLevel above MAX_DISCARD_LEVEL, which would
// cause them to not be down-scaled so they would get stuck at 0 discard all the time.
mMaxDiscardLevel = llmin(mMaxDiscardLevel, (S8)MAX_DISCARD_LEVEL);
// </FS:minerjr> [FIRE-35361]
}
}
else
@ -1097,6 +1102,8 @@ void sub_image_lines(U32 target, S32 miplevel, S32 x_offset, S32 y_offset, S32 w
// full width texture, do 32 lines at a time
for (U32 y_pos = y_offset; y_pos < y_offset_end; y_pos += batch_size)
{
// If this keeps crashing, pass down data_size, looks like it is using
// imageraw->getData(); for data, but goes way over allocated size limit
glTexSubImage2D(target, miplevel, x_offset, y_pos, width, batch_size, pixformat, pixtype, src);
src += line_width * batch_size;
}
@ -1106,6 +1113,8 @@ void sub_image_lines(U32 target, S32 miplevel, S32 x_offset, S32 y_offset, S32 w
// partial width or strange height
for (U32 y_pos = y_offset; y_pos < y_offset_end; y_pos += 1)
{
// If this keeps crashing, pass down data_size, looks like it is using
// imageraw->getData(); for data, but goes way over allocated size limit
glTexSubImage2D(target, miplevel, x_offset, y_pos, width, 1, pixformat, pixtype, src);
src += line_width;
}
@ -1546,6 +1555,7 @@ bool LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S
llassert(mCurrentDiscardLevel >= 0);
discard_level = mCurrentDiscardLevel;
}
discard_level = llmin(discard_level, MAX_DISCARD_LEVEL);
// Actual image width/height = raw image width/height * 2^discard_level
S32 raw_w = imageraw->getWidth() ;
@ -1644,6 +1654,7 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_
discard_level = mCurrentDiscardLevel;
}
discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel);
discard_level = llmin(discard_level, MAX_DISCARD_LEVEL);
if (main_thread // <--- always force creation of new_texname when not on main thread ...
&& !defer_copy // <--- ... or defer copy is set

View File

@ -167,6 +167,7 @@ set(viewer_SOURCE_FILES
fsparticipantlist.cpp
fspose.cpp
fsposeranimator.cpp
fsposestate.cpp
fsposingmotion.cpp
fsprimfeedauth.cpp
fsradar.cpp
@ -1008,6 +1009,7 @@ set(viewer_HEADER_FILES
fsparticipantlist.h
fspose.h
fsposeranimator.h
fsposestate.h
fsposingmotion.h
fsprimfeedauth.h
fsradar.h

View File

@ -14011,13 +14011,13 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>SceneLoadRearMaxRadiusFraction</key>
<map>
<key>Comment</key>
<string>a percentage of draw distance beyond which all objects outside of view frustum will be unloaded, regardless of pixel threshold</string>
<string>a fraction of draw distance beyond which all objects outside of view frustum will be unloaded, regardless of pixel threshold</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>75.0</real>
<real>0.75</real>
</map>
<key>SceneLoadRearPixelThreshold</key>
<map>
@ -23397,6 +23397,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>0</integer>
</map>
<key>DisableCameraJoystickCenterReset</key>
<map>
<key>Comment</key>
<string>Disable center reset on camera joysticks (bullseye) in camera controls.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSNetMapScripted</key>
<map>
<key>Comment</key>
@ -26576,6 +26587,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>0</integer>
</map>
<key>FSSnapshotShowGuides</key>
<map>
<key>Comment</key>
<string>If enabled, shows composition guides inside the snapshot frame.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSSnapshotFrameBorderColor</key>
<map>
<key>Comment</key>

View File

@ -49,10 +49,10 @@ void main()
else
{
// Draw border around the snapshot frame
if ((tc.x >= border_rect_px.x && tc.x < frame_rect_px.x) ||
(tc.x <= border_rect_px.z && tc.x > frame_rect_px.z) ||
(tc.y >= border_rect_px.y && tc.y < frame_rect_px.y) ||
(tc.y <= border_rect_px.w && tc.y > frame_rect_px.w))
if ((tc.x >= border_rect_px.x && tc.x < frame_rect_px.x) ||
(tc.x > frame_rect_px.z && tc.x <= border_rect_px.z) ||
(tc.y >= border_rect_px.y && tc.y < frame_rect_px.y) ||
(tc.y > frame_rect_px.w && tc.y <= border_rect_px.w))
{
diff.rgb = mix(diff.rgb, border_color, 0.5);
}

View File

@ -45,6 +45,7 @@
#include "llvoavatarself.h"
#include "llinventoryfunctions.h"
#include "lltoolcomp.h"
#include "llloadingindicator.h"
namespace
{
@ -110,6 +111,8 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
mCommitCallbackRegistrar.add("Poser.CommitSpinner", [this](LLUICtrl* spinner, const LLSD& data) { onCommitSpinner(spinner, data); });
mCommitCallbackRegistrar.add("Poser.CommitSlider", [this](LLUICtrl* slider, const LLSD& data) { onCommitSlider(slider, data); });
mCommitCallbackRegistrar.add("Poser.Symmetrize", [this](LLUICtrl*, const LLSD& data) { onClickSymmetrize(data); });
mLoadPoseTimer = new FSLoadPoseTimer(boost::bind(&FSFloaterPoser::timedReload, this));
}
bool FSFloaterPoser::postBuild()
@ -253,7 +256,6 @@ void FSFloaterPoser::onOpen(const LLSD& key)
LLFloater::onOpen(key);
}
void FSFloaterPoser::onFocusReceived()
{
LLEditMenuHandler::gEditMenuHandler = this;
@ -302,6 +304,7 @@ void FSFloaterPoser::onClose(bool app_quitting)
}
disableVisualManipulators();
delete mLoadPoseTimer;
LLFloater::onClose(app_quitting);
}
@ -512,9 +515,12 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
{
bool savingDiff = !mPoserAnimator.allBaseRotationsAreZero(avatar);
LLSD record;
record["version"]["value"] = (S32)6;
record["version"]["value"] = (S32)7;
record["startFromTeePose"]["value"] = !savingDiff;
if (savingDiff)
mPoserAnimator.savePosingState(avatar, &record);
LLVector3 rotation, position, scale, zeroVector;
bool baseRotationIsZero;
@ -523,12 +529,15 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
std::string bone_name = pj.jointName();
bool posingThisJoint = mPoserAnimator.isPosingAvatarJoint(avatar, pj);
bool jointRotLocked = mPoserAnimator.getRotationIsWorldLocked(avatar, pj);
bool jointRotMirrored = mPoserAnimator.getRotationIsMirrored(avatar, pj);
record[bone_name] = bone_name;
record[bone_name]["enabled"] = posingThisJoint;
record[bone_name] = bone_name;
record[bone_name]["enabled"] = posingThisJoint;
if (!posingThisJoint)
continue;
record[bone_name]["mirrored"] = jointRotMirrored;
if (!mPoserAnimator.tryGetJointSaveVectors(avatar, pj, &rotation, &position, &scale, &baseRotationIsZero))
continue;
@ -563,7 +572,6 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
return false;
}
return true;
}
@ -663,6 +671,8 @@ void FSFloaterPoser::onClickRecaptureSelectedBones()
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
mPoserAnimator.updatePosingState(avatar, selectedJoints);
for (auto item : selectedJoints)
{
bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item);
@ -892,14 +902,39 @@ void FSFloaterPoser::onPoseMenuAction(const LLSD& param)
else if (loadStyle == "selective_rot")
loadType = SELECTIVE_ROT;
mLoadPoseTimer->tryLoading(poseName, loadType);
setLoadingProgress(true);
}
void FSFloaterPoser::timedReload()
{
if (!mLoadPoseTimer)
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
loadPoseFromXml(avatar, poseName, loadType);
onJointTabSelect();
refreshJointScrollListMembers();
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
if (loadPoseFromXml(avatar, mLoadPoseTimer->getPosePath(), mLoadPoseTimer->getLoadMethod()))
{
mLoadPoseTimer->completeLoading();
onJointTabSelect();
refreshJointScrollListMembers();
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
}
if (mLoadPoseTimer->loadCompleteOrFailed())
setLoadingProgress(false);
}
void FSFloaterPoser::setLoadingProgress(bool started)
{
LLLoadingIndicator* mLoadingIndicator = findChild<LLLoadingIndicator>("progress_indicator");
if (!mLoadingIndicator)
return;
mLoadingIndicator->setVisible(started);
started ? mLoadingIndicator->start() : mLoadingIndicator->stop();
}
void FSFloaterPoser::onClickLoadLeftHandPose()
@ -1020,14 +1055,15 @@ bool FSFloaterPoser::poseFileStartsFromTeePose(const std::string& poseFileName)
return false;
}
void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod)
bool FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod)
{
bool loadSuccess = false;
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY);
if (!gDirUtilp->fileExists(pathname))
return;
return loadSuccess;
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
return loadSuccess;
std::string fullPath =
gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY, poseFileName + POSE_INTERNAL_FORMAT_FILE_EXT);
@ -1049,20 +1085,22 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
bool enabled;
bool setJointBaseRotationToZero;
bool worldLocked;
bool mirroredJoint;
S32 version = 0;
bool startFromZeroRot = true;
infile.open(fullPath);
if (!infile.is_open())
return;
return loadSuccess;
loadSuccess = true;
while (!infile.eof())
{
S32 lineCount = LLSDSerialize::fromXML(pose, infile);
if (lineCount == LLSDParser::PARSE_FAILURE)
{
LL_WARNS("Posing") << "Failed to parse file: " << poseFileName << LL_ENDL;
return;
return loadSuccess;
}
for (LLSD::map_const_iterator itr = pose.beginMap(); itr != pose.endMap(); ++itr)
@ -1108,32 +1146,44 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
setJointBaseRotationToZero = startFromZeroRot;
if (loadPositions && control_map.has("position"))
{
vec3.setValue(control_map["position"]);
mPoserAnimator.loadJointPosition(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3);
}
else
vec3.clear();
mPoserAnimator.loadJointPosition(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3);
if (loadRotations && control_map.has("rotation"))
{
vec3.setValue(control_map["rotation"]);
mPoserAnimator.loadJointRotation(avatar, poserJoint, setJointBaseRotationToZero, vec3);
}
else
vec3.clear();
mPoserAnimator.loadJointRotation(avatar, poserJoint, setJointBaseRotationToZero, vec3);
if (loadScales && control_map.has("scale"))
{
vec3.setValue(control_map["scale"]);
mPoserAnimator.loadJointScale(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3);
}
else
vec3.clear();
mPoserAnimator.loadJointScale(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3);
worldLocked = control_map.has("worldLocked") ? control_map["worldLocked"].asBoolean() : false;
mPoserAnimator.setRotationIsWorldLocked(avatar, *poserJoint, worldLocked);
mirroredJoint = control_map.has("mirrored") ? control_map["mirrored"].asBoolean() : false;
mPoserAnimator.setRotationIsMirrored(avatar, *poserJoint, mirroredJoint);
}
if (version > 6 && !startFromZeroRot)
loadSuccess = mPoserAnimator.loadPosingState(avatar, pose);
}
}
catch ( const std::exception & e )
{
loadSuccess = false;
LL_WARNS("Posing") << "Everything caught fire trying to load the pose: " << poseFileName << " exception: " << e.what() << LL_ENDL;
}
return loadSuccess;
}
void FSFloaterPoser::startPosingSelf()
@ -1777,6 +1827,9 @@ void FSFloaterPoser::setUiSelectedAvatarSaveFileName(const std::string& saveFile
LLVOAvatar* FSFloaterPoser::getAvatarByUuid(const LLUUID& avatarToFind) const
{
if (avatarToFind.isNull())
return nullptr;
for (LLCharacter* character : LLCharacter::sInstances)
{
if (avatarToFind != character->getID())
@ -2720,3 +2773,30 @@ void FSFloaterPoser::onClickLockWorldRotBtn()
refreshTextHighlightingOnJointScrollLists();
}
FSLoadPoseTimer::FSLoadPoseTimer(FSLoadPoseTimer::callback_t callback) : LLEventTimer(0.5f), mCallback(callback)
{
}
bool FSLoadPoseTimer::tick()
{
if (!mAttemptLoading)
return false;
if (mCallback.empty())
return false;
if (mLoadAttempts >= mMaxLoadAttempts)
mAttemptLoading = false;
mLoadAttempts++;
mCallback();
return false;
}
void FSLoadPoseTimer::tryLoading(std::string filePath, E_LoadPoseMethods loadMethod)
{
mPoseFullPath = filePath;
mLoadType = loadMethod;
mLoadAttempts = 0;
mAttemptLoading = true;
}

View File

@ -39,6 +39,7 @@ class LLLineEditor;
class LLScrollListCtrl;
class LLSliderCtrl;
class LLTabContainer;
class FSLoadPoseTimer;
/// <summary>
/// Describes how to load a pose file.
@ -228,11 +229,12 @@ public:
bool savePoseToBvh(LLVOAvatar* avatar, const std::string& posePath);
void onClickBrowsePoseCache();
void onPoseMenuAction(const LLSD& param);
void loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod);
bool loadPoseFromXml(LLVOAvatar* avatar, const std::string& poseFileName, E_LoadPoseMethods loadMethod);
bool poseFileStartsFromTeePose(const std::string& poseFileName);
void setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName();
void setUiSelectedAvatarSaveFileName(const std::string& saveFileName);
bool confirmFileOverwrite(std::string fileName);
void timedReload();
void setLoadingProgress(bool started);
void startPosingSelf();
void stopPosingAllAvatars();
// visual manipulators control
@ -502,6 +504,8 @@ public:
LLLineEditor* mPoseSaveNameEditor{ nullptr };
FSLoadPoseTimer* mLoadPoseTimer;
LLPanel* mJointsParentPnl{ nullptr };
LLPanel* mTrackballPnl{ nullptr };
LLPanel* mPositionRotationPnl{ nullptr };
@ -530,4 +534,27 @@ public:
LLUICtrl* mScaleZSpnr{ nullptr };
};
class FSLoadPoseTimer : public LLEventTimer
{
public:
typedef boost::function<void()> callback_t;
FSLoadPoseTimer(callback_t callback);
/*virtual*/ bool tick();
void tryLoading(std::string filePath, E_LoadPoseMethods loadMethod);
bool loadCompleteOrFailed() const { return !mAttemptLoading && mLoadAttempts > 0; }
void completeLoading() { mAttemptLoading = false; }
std::string getPosePath() { return mPoseFullPath; };
E_LoadPoseMethods getLoadMethod() const { return mLoadType; };
private:
callback_t mCallback;
bool mAttemptLoading = false;
E_LoadPoseMethods mLoadType = ROT_POS_AND_SCALES;
std::string mPoseFullPath;
int mLoadAttempts = 0;
const int mMaxLoadAttempts = 5;
};
#endif

View File

@ -55,6 +55,7 @@ void FSJointPose::setPublicPosition(const LLVector3& pos)
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mPosition.set(pos);
mCurrentState.mLastChangeWasRotational = false;
}
void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
@ -65,17 +66,22 @@ void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
zeroBaseRotation(true);
mCurrentState.mRotation.set(rot);
mCurrentState.mLastChangeWasRotational = true;
}
void FSJointPose::setPublicScale(const LLVector3& scale)
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mScale.set(scale);
mCurrentState.mLastChangeWasRotational = false;
}
void FSJointPose::undoLastChange()
bool FSJointPose::undoLastChange()
{
mCurrentState = undoLastStateChange(FSJointState(mCurrentState));
bool changeType = mCurrentState.mLastChangeWasRotational;
mCurrentState = undoLastStateChange(FSJointState(mCurrentState));
return changeType;
}
void FSJointPose::redoLastChange()
@ -87,6 +93,7 @@ void FSJointPose::resetJoint()
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.resetJoint();
mCurrentState.mLastChangeWasRotational = true;
}
void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo)
@ -153,6 +160,7 @@ void FSJointPose::recaptureJoint()
addStateToUndo(FSJointState(mCurrentState));
mCurrentState = FSJointState(joint);
mCurrentState.mLastChangeWasRotational = true;
}
LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase)
@ -162,9 +170,30 @@ LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase)
return LLQuaternion::DEFAULT;
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.mLastChangeWasRotational = true;
return mCurrentState.updateFromJoint(joint, zeroBase);
}
void FSJointPose::setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority)
{
mCurrentState.resetBaseRotation(rotation, priority);
}
void FSJointPose::setBasePosition(LLVector3 position, LLJoint::JointPriority priority)
{
mCurrentState.resetBasePosition(position, priority);
}
void FSJointPose::setBaseScale(LLVector3 scale, LLJoint::JointPriority priority)
{
mCurrentState.resetBaseScale(scale, priority);
}
void FSJointPose::setJointPriority(LLJoint::JointPriority priority)
{
mCurrentState.setPriority(priority);
}
void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
{
if (!oppositeJoint)
@ -177,6 +206,18 @@ void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
oppositeJoint->mCurrentState.cloneRotationFrom(tempState);
}
void FSJointPose::swapBaseRotationWith(FSJointPose* oppositeJoint)
{
if (!oppositeJoint)
return;
if (mIsCollisionVolume)
return;
auto tempState = FSJointState(mCurrentState);
mCurrentState.cloneBaseRotationFrom(oppositeJoint->mCurrentState);
oppositeJoint->mCurrentState.cloneBaseRotationFrom(tempState);
}
void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint)
{
if (!fromJoint)
@ -184,6 +225,7 @@ void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint)
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.cloneRotationFrom(fromJoint->mCurrentState);
mCurrentState.mLastChangeWasRotational = true;
}
void FSJointPose::mirrorRotationFrom(FSJointPose* fromJoint)
@ -208,6 +250,14 @@ void FSJointPose::reflectRotation()
mCurrentState.reflectRotation();
}
void FSJointPose::reflectBaseRotation()
{
if (mIsCollisionVolume)
return;
mCurrentState.reflectBaseRotation();
}
void FSJointPose::zeroBaseRotation(bool lockInBvh)
{
if (mIsCollisionVolume)
@ -244,20 +294,24 @@ bool FSJointPose::userHasSetBaseRotationToZero() const
bool FSJointPose::getWorldRotationLockState() const
{
if (mIsCollisionVolume)
return false;
return mCurrentState.mRotationIsWorldLocked;
}
void FSJointPose::setWorldRotationLockState(bool newState)
{
if (mIsCollisionVolume)
return;
mCurrentState.mRotationIsWorldLocked = newState;
}
bool FSJointPose::getRotationMirrorState() const
{
return mCurrentState.mJointRotationIsMirrored;
}
void FSJointPose::setRotationMirrorState(bool newState)
{
mCurrentState.mJointRotationIsMirrored = newState;
}
bool FSJointPose::canPerformUndo() const
{
switch (mLastSetJointStates.size())

View File

@ -70,7 +70,8 @@ class FSJointPose
/// <summary>
/// Undoes the last position set, if any.
/// </summary>
void undoLastChange();
/// <returns>true if the change we un-did was rotational.</returns>
bool undoLastChange();
/// <summary>
/// Undoes the last position set, if any.
@ -104,6 +105,11 @@ class FSJointPose
/// </summary>
void reflectRotation();
/// <summary>
/// Reflects the base rotation of the represented joint left-right.
/// </summary>
void reflectBaseRotation();
/// <summary>
/// Sets the private rotation of the represented joint to zero.
/// </summary>
@ -143,6 +149,11 @@ class FSJointPose
/// </summary>
void swapRotationWith(FSJointPose* oppositeJoint);
/// <summary>
/// Exchanges the base rotations between two joints.
/// </summary>
void swapBaseRotationWith(FSJointPose* oppositeJoint);
/// <summary>
/// Clones the rotation to this from the supplied joint.
/// </summary>
@ -165,6 +176,33 @@ class FSJointPose
/// <returns>The rotation of the public difference between before and after recapture.</returns>
LLQuaternion recaptureJointAsDelta(bool zeroBase);
/// <summary>
/// Sets the base rotation to the supplied rotation if the supplied priority is appropriate.
/// </summary>
/// <param name="rotation">The base rotation to set; zero is ignored.</param>
/// <param name="priority">The priority of the base rotation; only priority equal or higher than any prior sets have any effect.</param>
void setBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority);
/// <summary>
/// Sets the base position to the supplied position if the supplied priority is appropriate.
/// </summary>
/// <param name="position">The base position to set; zero is ignored.</param>
/// <param name="priority">The priority of the base rotation; only priority equal or higher than any prior sets have any effect.</param>
void setBasePosition(LLVector3 position, LLJoint::JointPriority priority);
/// <summary>
/// Sets the base scale to the supplied scale if the supplied priority is appropriate.
/// </summary>
/// <param name="scale">The base scale to set; zero is ignored.</param>
/// <param name="priority">The priority of the base rotation; only priority equal or higher than any prior sets have any effect.</param>
void setBaseScale(LLVector3 scale, LLJoint::JointPriority priority);
/// <summary>
/// Sets the priority of the bone to the supplied value.
/// </summary>
/// <param name="priority">The new priority of the base rotation.</param>
void setJointPriority(LLJoint::JointPriority priority);
/// <summary>
/// Clears the undo/redo deque.
/// </summary>
@ -188,6 +226,18 @@ class FSJointPose
/// <param name="newState">The new state for the world-rotation lock.</param>
void setWorldRotationLockState(bool newState);
/// <summary>
/// Gets whether the rotation of a joint has been mirrored.
/// </summary>
/// <returns>True if the joint has been mirrored, otherwise false.</returns>
bool getRotationMirrorState() const;
/// <summary>
/// Sets whether the rotation of a joint has been mirrored.
/// </summary>
/// <param name="newState">The new state for the mirror.</param>
void setRotationMirrorState(bool newState);
/// <summary>
/// Reverts the position/rotation/scale to their values when the animation begun.
/// This treatment is required for certain joints, particularly Collision Volumes and those bones not commonly animated by an AO.
@ -221,32 +271,50 @@ class FSJointPose
void reflectRotation()
{
mBaseRotation.mQ[VX] *= -1;
mBaseRotation.mQ[VZ] *= -1;
reflectBaseRotation();
mRotation.mQ[VX] *= -1;
mRotation.mQ[VZ] *= -1;
mJointRotationIsMirrored = !mJointRotationIsMirrored;
}
void reflectBaseRotation()
{
mBaseRotation.mQ[VX] *= -1;
mBaseRotation.mQ[VZ] *= -1;
}
void cloneRotationFrom(FSJointState otherState)
{
mBaseRotation.set(otherState.mBaseRotation);
cloneBaseRotationFrom(otherState);
mRotation.set(otherState.mRotation);
mUserSpecifiedBaseZero = otherState.mUserSpecifiedBaseZero;
}
void cloneBaseRotationFrom(FSJointState otherState)
{
mBaseRotation.set(otherState.mBaseRotation);
}
bool baseRotationIsZero() const { return mBaseRotation == LLQuaternion::DEFAULT; }
void resetJoint()
{
mUserSpecifiedBaseZero = false;
mRotationIsWorldLocked = false;
mUserSpecifiedBaseZero = false;
mRotationIsWorldLocked = false;
mJointRotationIsMirrored = false;
mLastChangeWasRotational = true;
mBaseRotation.set(mStartingRotation);
mRotation.set(LLQuaternion::DEFAULT);
mPosition.setZero();
mScale.setZero();
}
void zeroBaseRotation() { mBaseRotation = LLQuaternion::DEFAULT; }
void zeroBaseRotation()
{
mBasePriority = LLJoint::LOW_PRIORITY;
mBaseRotation = LLQuaternion::DEFAULT;
mJointRotationIsMirrored = false;
}
void revertJointToBase(LLJoint* joint) const
{
@ -281,6 +349,44 @@ class FSJointPose
return newPublicRot *= ~initalPublicRot;
}
void resetBaseRotation(LLQuaternion rotation, LLJoint::JointPriority priority)
{
if (mUserSpecifiedBaseZero)
return;
if (priority < mBasePriority)
return;
if (rotation == LLQuaternion::DEFAULT)
return;
mBasePriority = priority;
mBaseRotation.set(rotation);
}
void resetBasePosition(LLVector3 position, LLJoint::JointPriority priority)
{
if (priority < mBasePriority)
return;
mBasePriority = priority;
mBasePosition.set(position);
}
void resetBaseScale(LLVector3 scale, LLJoint::JointPriority priority)
{
if (priority < mBasePriority)
return;
if (scale.isExactlyZero())
return;
mBasePriority = priority;
mBaseScale.set(scale);
}
void setPriority(LLJoint::JointPriority priority) { mBasePriority = priority; }
private:
FSJointState(FSJointState* state)
{
@ -292,8 +398,12 @@ class FSJointPose
mRotation.set(state->mRotation);
mPosition.set(state->mPosition);
mScale.set(state->mScale);
mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero;
mRotationIsWorldLocked = state->mRotationIsWorldLocked;
mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero;
mRotationIsWorldLocked = state->mRotationIsWorldLocked;
mBasePriority = state->mBasePriority;
mJointRotationIsMirrored = state->mJointRotationIsMirrored;
mLastChangeWasRotational = state->mLastChangeWasRotational;
}
public:
@ -301,6 +411,15 @@ class FSJointPose
LLVector3 mPosition;
LLVector3 mScale;
bool mRotationIsWorldLocked = false;
bool mLastChangeWasRotational = false;
/// <summary>
/// Whether the joint has been mirrored.
/// </summary>
/// <remarks>
/// Used when loading a diff; indicating that the base-rotations, once restored, need to be swapped.
/// </remarks>
bool mJointRotationIsMirrored = false;
/// <summary>
/// A value indicating whether the user has explicitly set the base rotation to zero.
@ -318,6 +437,7 @@ class FSJointPose
LLQuaternion mBaseRotation;
LLVector3 mBasePosition;
LLVector3 mBaseScale;
LLJoint::JointPriority mBasePriority = LLJoint::LOW_PRIORITY;
};
private:

View File

@ -94,8 +94,8 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!jointPose)
return;
jointPose->undoLastChange();
undoOrRedoWorldLockedDescendants(joint, posingMotion, false);
if (jointPose->undoLastChange())
undoOrRedoWorldLockedDescendants(joint, posingMotion, false);
if (style == NONE || style == DELTAMODE)
return;
@ -104,7 +104,8 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!oppositeJointPose)
return;
oppositeJointPose->undoLastChange();
if (!oppositeJointPose->undoLastChange())
return;
auto oppositePoserJoint = getPoserJointByName(joint.mirrorJointName());
if (oppositePoserJoint)
@ -271,6 +272,38 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j
}
}
bool FSPoserAnimator::getRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
if (!isAvatarSafeToUse(avatar))
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return false;
return jointPose->getRotationMirrorState();
}
void FSPoserAnimator::setRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState)
{
if (!isAvatarSafeToUse(avatar))
return;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return;
jointPose->setRotationMirrorState(newState);
}
bool FSPoserAnimator::getRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
if (!isAvatarSafeToUse(avatar))
@ -901,7 +934,10 @@ void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint*
if (loadPositionAsDelta)
jointPose->setPublicPosition(position);
else
jointPose->setPublicPosition(position);
{
jointPose->setJointPriority(LLJoint::LOW_PRIORITY);
jointPose->setBasePosition(position, LLJoint::LOW_PRIORITY);
}
}
void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale)
@ -920,7 +956,60 @@ void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joi
if (loadScaleAsDelta)
jointPose->setPublicScale(scale);
else
jointPose->setPublicScale(scale);
{
jointPose->setJointPriority(LLJoint::LOW_PRIORITY);
jointPose->setBaseScale(scale, LLJoint::LOW_PRIORITY);
jointPose->setPublicScale(LLVector3::zero);
}
}
bool FSPoserAnimator::loadPosingState(LLVOAvatar* avatar, LLSD pose)
{
if (!isAvatarSafeToUse(avatar))
return false;
mPosingState.purgeMotionStates(avatar);
mPosingState.restoreMotionStates(avatar, pose);
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
bool loadSuccess = mPosingState.applyMotionStatesToPosingMotion(avatar, posingMotion);
if (loadSuccess)
applyJointMirrorToBaseRotations(posingMotion);
return loadSuccess;
}
void FSPoserAnimator::applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion)
{
for (size_t index = 0; index != PoserJoints.size(); ++index)
{
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(PoserJoints[index].jointName());
if (!jointPose)
continue;
if (!jointPose->getRotationMirrorState())
continue;
if (PoserJoints[index].dontFlipOnMirror()) // we only flip one side.
continue;
jointPose->reflectBaseRotation();
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(PoserJoints[index].mirrorJointName());
if (!oppositeJointPose)
continue;
oppositeJointPose->reflectBaseRotation();
jointPose->swapBaseRotationWith(oppositeJointPose);
}
}
void FSPoserAnimator::savePosingState(LLVOAvatar* avatar, LLSD* saveRecord)
{
mPosingState.writeMotionStates(avatar, saveRecord);
}
const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) const
@ -948,6 +1037,8 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar)
if (avatar->isSelf())
gAgent.stopFidget();
mPosingState.captureMotionStates(avatar);
avatar->startDefaultMotions();
avatar->startMotion(posingMotion->motionId());
@ -957,6 +1048,22 @@ bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar)
return false;
}
void FSPoserAnimator::updatePosingState(LLVOAvatar* avatar, std::vector<FSPoserAnimator::FSPoserJoint*> jointsRecaptured)
{
if (!avatar)
return;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return;
std::string jointNamesRecaptured;
for (auto item : jointsRecaptured)
jointNamesRecaptured += item->jointName();
mPosingState.updateMotionStates(avatar, posingMotion, jointNamesRecaptured);
}
void FSPoserAnimator::stopPosingAvatar(LLVOAvatar *avatar)
{
if (!avatar || avatar->isDead())
@ -966,6 +1073,7 @@ void FSPoserAnimator::stopPosingAvatar(LLVOAvatar *avatar)
if (!posingMotion)
return;
mPosingState.purgeMotionStates(avatar);
avatar->stopMotion(posingMotion->motionId());
}
@ -1006,7 +1114,6 @@ FSPosingMotion* FSPoserAnimator::findOrCreatePosingMotion(LLVOAvatar* avatar)
sAvatarIdToRegisteredAnimationId[avatar->getID()] = animationAssetId;
return dynamic_cast<FSPosingMotion*>(avatar->createMotion(animationAssetId));
}
bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const
@ -1118,7 +1225,11 @@ void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& join
if (jointPose->getWorldRotationLockState())
{
redo ? jointPose->redoLastChange() : jointPose->undoLastChange();
if (redo)
jointPose->redoLastChange();
else
jointPose->undoLastChange();
return;
}

View File

@ -28,6 +28,7 @@
#define LL_FSPoserAnimator_H
#include "fsposingmotion.h"
#include "fsposestate.h"
#include "llvoavatar.h"
/// <summary>
@ -197,27 +198,27 @@ public:
/// </remarks>
const std::vector<FSPoserJoint> PoserJoints{
// head, torso, legs
{ "mHead", "", BODY, { "mEyeLeft", "mEyeRight", "mFaceRoot", "mSkull" }, "0.000 0.076 0.000" },
{ "mNeck", "", BODY, { "mHead" }, "0.000 0.251 -0.010" },
{ "mPelvis", "", WHOLEAVATAR, { "mSpine1", "mHipLeft", "mHipRight", "mTail1", "mGroin", "mHindLimbsRoot" }, "0.000000 0.000000 0.000000" },
{ "mChest", "", BODY, { "mNeck", "mCollarLeft", "mCollarRight", "mWingsRoot" }, "0.000 0.205 -0.015" },
{ "mTorso", "", BODY, { "mSpine3" }, "0.000 0.084 0.000" },
{ "mCollarLeft", "mCollarRight", BODY, { "mShoulderLeft" }, "0.085 0.165 -0.021" },
{ "mShoulderLeft", "mShoulderRight", BODY, { "mElbowLeft" }, "0.079 0.000 0.000" },
{ "mElbowLeft", "mElbowRight", BODY, { "mWristLeft" }, "0.248 0.000 0.000" },
{ "mWristLeft", "mWristRight", BODY, { "mHandThumb1Left", "mHandIndex1Left", "mHandMiddle1Left", "mHandRing1Left", "mHandPinky1Left" }, "0.205 0.000 0.000" },
{ "mCollarRight", "mCollarLeft", BODY, { "mShoulderRight" }, "-0.085 0.165 -0.021", "", true },
{ "mShoulderRight", "mShoulderLeft", BODY, { "mElbowRight" }, "-0.079 0.000 0.000", "", true },
{ "mElbowRight", "mElbowLeft", BODY, { "mWristRight" }, "-0.248 0.000 0.000", "", true },
{ "mWristRight", "mWristLeft", BODY, { "mHandThumb1Right", "mHandIndex1Right", "mHandMiddle1Right", "mHandRing1Right", "mHandPinky1Right" }, "-0.205 0.000 0.000", "", true },
{ "mHipLeft", "mHipRight", BODY, { "mKneeLeft" }, "0.127 -0.041 0.034" },
{ "mKneeLeft", "mKneeRight", BODY, { "mAnkleLeft" }, "-0.046 -0.491 -0.001" },
{ "mAnkleLeft", "mAnkleRight", BODY, { "mFootLeft" }, "0.001 -0.468 -0.029" },
{ "mHead", "", BODY, { "mEyeLeft", "mEyeRight", "mFaceRoot", "mSkull", "HEAD" }, "0.000 0.076 0.000" },
{ "mNeck", "", BODY, { "mHead", "NECK" }, "0.000 0.251 -0.010" },
{ "mPelvis", "", WHOLEAVATAR, { "mSpine1", "mHipLeft", "mHipRight", "mTail1", "mGroin", "mHindLimbsRoot", "PELVIS", "BUTT" }, "0.000000 0.000000 0.000000" },
{ "mChest", "", BODY, { "mNeck", "mCollarLeft", "mCollarRight", "mWingsRoot", "CHEST", "LEFT_PEC", "RIGHT_PEC", "UPPER_BACK" }, "0.000 0.205 -0.015" },
{ "mTorso", "", BODY, { "mSpine3", "BELLY", "LEFT_HANDLE", "RIGHT_HANDLE", "LOWER_BACK" }, "0.000 0.084 0.000" },
{ "mCollarLeft", "mCollarRight", BODY, { "mShoulderLeft", "L_CLAVICLE" }, "0.085 0.165 -0.021" },
{ "mShoulderLeft", "mShoulderRight", BODY, { "mElbowLeft", "L_UPPER_ARM" }, "0.079 0.000 0.000" },
{ "mElbowLeft", "mElbowRight", BODY, { "mWristLeft", "L_LOWER_ARM" }, "0.248 0.000 0.000" },
{ "mWristLeft", "mWristRight", BODY, { "mHandThumb1Left", "mHandIndex1Left", "mHandMiddle1Left", "mHandRing1Left", "mHandPinky1Left", "L_HAND" }, "0.205 0.000 0.000" },
{ "mCollarRight", "mCollarLeft", BODY, { "mShoulderRight", "R_CLAVICLE" }, "-0.085 0.165 -0.021", "", true },
{ "mShoulderRight", "mShoulderLeft", BODY, { "mElbowRight", "R_UPPER_ARM" }, "-0.079 0.000 0.000", "", true },
{ "mElbowRight", "mElbowLeft", BODY, { "mWristRight", "R_LOWER_ARM" }, "-0.248 0.000 0.000", "", true },
{ "mWristRight", "mWristLeft", BODY, { "mHandThumb1Right", "mHandIndex1Right", "mHandMiddle1Right", "mHandRing1Right", "mHandPinky1Right", "R_HAND" }, "-0.205 0.000 0.000", "", true },
{ "mHipLeft", "mHipRight", BODY, { "mKneeLeft", "L_UPPER_LEG" }, "0.127 -0.041 0.034" },
{ "mKneeLeft", "mKneeRight", BODY, { "mAnkleLeft", "L_LOWER_LEG" }, "-0.046 -0.491 -0.001" },
{ "mAnkleLeft", "mAnkleRight", BODY, { "mFootLeft", "L_FOOT" }, "0.001 -0.468 -0.029" },
{ "mFootLeft", "mFootRight", BODY, { "mToeLeft" }, "0.000 -0.061 0.112" },
{ "mToeLeft", "mToeRight", BODY, {}, "0.000 0.000 0.109", "0.000 0.020 0.000" },
{ "mHipRight", "mHipLeft", BODY, { "mKneeRight" }, "-0.129 -0.041 0.034", "", true },
{ "mKneeRight", "mKneeLeft", BODY, { "mAnkleRight" }, "0.049 -0.491 -0.001", "", true },
{ "mAnkleRight", "mAnkleLeft", BODY, { "mFootRight" }, "0.000 -0.468 -0.029", "", true },
{ "mHipRight", "mHipLeft", BODY, { "mKneeRight", "R_UPPER_LEG" }, "-0.129 -0.041 0.034", "", true },
{ "mKneeRight", "mKneeLeft", BODY, { "mAnkleRight", "R_LOWER_LEG" }, "0.049 -0.491 -0.001", "", true },
{ "mAnkleRight", "mAnkleLeft", BODY, { "mFootRight", "R_FOOT" }, "0.000 -0.468 -0.029", "", true },
{ "mFootRight", "mFootLeft", BODY, { "mToeRight" }, "0.000 -0.061 0.112", "", true },
{ "mToeRight", "mToeLeft", BODY, {}, "0.000 0.000 0.109", "0.000 0.020 0.000", true },
@ -617,6 +618,22 @@ public:
/// <param name="newState">The lock state to apply.</param>
void setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState);
/// <summary>
/// Gets whether the supplied joint for the supplied avatar has been mirrored.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>True if the joint is maintaining a fixed-rotation in world, otherwise false.</returns>
bool getRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
/// <summary>
/// Sets the mirrored status for supplied joint for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <param name="newState">The mirror state to apply.</param>
void setRotationIsMirrored(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState);
/// <summary>
/// Determines if the kind of save to perform should be a 'delta' save, or a complete save.
/// </summary>
@ -690,6 +707,53 @@ public:
/// </remarks>
void loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale);
/// <summary>
/// Loads the posing state (base rotations) to the supplied avatars posing-motion, from the supplied record.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be loaded.</param>
/// <param name="pose">The record to read the posing state from.</param>
/// <returns>True if the pose loaded successfully, otherwise false.</returns>
/// <remarks>
/// When a save embeds animations that need to be restored at a certain time,
/// it can take several frames for the animation to be loaded and ready.
/// It may therefore be necessary to attempt this several times.
/// </remarks>
bool loadPosingState(LLVOAvatar* avatar, LLSD pose);
/// <summary>
/// Adds the posing state for the supplied avatar to the supplied record.
/// </summary>
/// <param name="avatar">That avatar whose posing state should be written.</param>
/// <param name="saveRecord">The record to write the posing state to.</param>
void savePosingState(LLVOAvatar* avatar, LLSD* saveRecord);
/// <summary>
/// Purges and recaptures the pose state for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar whose pose state is to be recapture.</param>
/// <param name="jointsRecaptured">The joints which were recaptured.</param>
void updatePosingState(LLVOAvatar* avatar, std::vector<FSPoserAnimator::FSPoserJoint*> jointsRecaptured);
/// <summary>
/// Add a new posing state, or updates the matching posing state with the supplied data.
/// </summary>
/// <param name="avatar">The avatar the posing state is intended for.</param>
/// <param name="animId">The ID of the animation.</param>
/// <param name="updateTime">The frame-time of the animation.</param>
/// <param name="jointNames">The names of the joints, if any, the animation should specifically be applied to.</param>
/// <param name="captureOrder">The capture order.</param>
/// <returns>True if the posing state was added or changed by the update data, otherwise false.</returns>
bool addOrUpdatePosingState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder);
/// <summary>
/// Traverses the joints and applies reversals to the base rotations if needed.
/// </summary>
/// <param name="posingMotion">The posing motion whose pose states require updating.</param>
/// <remarks>
/// Required after restoring a diff. The base rotations will be in their original arrangment.
/// </remarks>
void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion);
private:
/// <summary>
/// Translates a rotation vector from the UI to a Quaternion for the bone.
@ -788,6 +852,8 @@ public:
/// Is static, so the animationId is not lost between sessions (such as when the UI floater is closed and reopened).
/// </summary>
static std::map<LLUUID, LLAssetID> sAvatarIdToRegisteredAnimationId;
FSPoseState mPosingState;
};
#endif // LL_FSPoserAnimator_H

View File

@ -0,0 +1,268 @@
#include "fsposestate.h"
#include "llinventorymodel.h" // gInventory
std::map<LLUUID, std::vector<FSPoseState::fsMotionState>> FSPoseState::sMotionStates;
std::map<LLUUID, int> FSPoseState::sCaptureOrder;
void FSPoseState::captureMotionStates(LLVOAvatar* avatar)
{
if (!avatar)
return;
sCaptureOrder[avatar->getID()] = 0;
for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); ++anim_it)
{
LLKeyframeMotion* motion = dynamic_cast<LLKeyframeMotion*>(avatar->findMotion(anim_it->first));
if (!motion)
continue;
fsMotionState newState;
newState.motionId = anim_it->first;
newState.lastUpdateTime = motion->getLastUpdateTime();
newState.captureOrder = 0;
newState.avatarOwnsPose = canSaveMotionId(avatar, anim_it->first);
sMotionStates[avatar->getID()].push_back(newState);
}
}
void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured)
{
if (!avatar || !posingMotion)
return;
sCaptureOrder[avatar->getID()]++;
// if an animation for avatar is a subset of jointNamesRecaptured, 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)
it = sMotionStates[avatar->getID()].erase(it);
else
it++;
}
for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); anim_it++)
{
LLKeyframeMotion* motion = dynamic_cast<LLKeyframeMotion*>(avatar->findMotion(anim_it->first));
if (!motion)
continue;
if (!posingMotion->otherMotionAnimatesJoints(motion, jointNamesRecaptured))
continue;
bool foundMatch = false;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
bool motionIdMatches = (*it).motionId == anim_it->first;
bool updateTimesMatch = (*it).lastUpdateTime == motion->getLastUpdateTime(); // consider when recapturing the same animation at different times for a subset of bones
foundMatch = motionIdMatches && updateTimesMatch;
if (foundMatch)
break;
}
if (foundMatch)
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);
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)
return;
sMotionStates[avatar->getID()].clear();
}
void FSPoseState::writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord)
{
if (!avatar)
return;
int animNumber = 0;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); ++it)
{
if (!it->avatarOwnsPose)
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;
}
}
void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, LLSD pose)
{
if (!avatar)
return;
sCaptureOrder[avatar->getID()] = 0;
for (auto itr = pose.beginMap(); itr != pose.endMap(); ++itr)
{
std::string const& name = itr->first;
LLSD const& control_map = itr->second;
if (!name.starts_with("poseState"))
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;
}
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("captureOrder"))
newState.captureOrder = control_map["captureOrder"].asInteger();
if (newState.captureOrder > sCaptureOrder[avatar->getID()])
sCaptureOrder[avatar->getID()] = newState.captureOrder;
sMotionStates[avatar->getID()].push_back(newState);
}
}
bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion)
{
if (!avatar || !posingMotion)
return false;
bool allMotionsApplied = true;
std::sort(sMotionStates[avatar->getID()].begin(), sMotionStates[avatar->getID()].end(), compareByCaptureOrder());
int lastCaptureOrder = 0;
bool needPriorityReset = false;
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
needPriorityReset = it->captureOrder > lastCaptureOrder;
if (it->motionApplied)
continue;
LLKeyframeMotion* kfm = dynamic_cast<LLKeyframeMotion*>(avatar->findMotion(it->motionId));
if (kfm)
{
if (needPriorityReset)
{
lastCaptureOrder = it->captureOrder;
resetPriorityForCaptureOrder(avatar, posingMotion, lastCaptureOrder);
}
it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNamesAnimated);
}
else
{
avatar->startMotion(it->motionId); // only start if not a kfm; then wait until it casts as a kfm
avatar->stopMotion(it->motionId); // only stop if we have used it and we started it
}
allMotionsApplied &= it->motionApplied;
}
return allMotionsApplied;
}
void FSPoseState::resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder)
{
for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++)
{
if (it->jointNamesAnimated.empty())
continue;
if (it->motionApplied)
continue;
if (it->captureOrder != captureOrder)
continue;
posingMotion->resetBonePriority(it->jointNamesAnimated);
}
}
bool FSPoseState::canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId)
{
if (!gAgentAvatarp || gAgentAvatarp.isNull())
return false;
// does the animation exist in inventory
LLInventoryItem* item = gInventory.getItem(motionId);
if (item && item->getPermissions().getOwner() == avatar->getID())
return true;
for (const auto& [anim_object_id, anim_anim_id] : gAgentAvatarp->mAnimationSources)
{
if (anim_anim_id != 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;
// is the item that start the animation in-world
LLViewerObject* object = gObjectList.findObject(anim_object_id);
if (object && object->permYouOwner())
return true;
return false;
}
return false;
}

172
indra/newview/fsposestate.h Normal file
View File

@ -0,0 +1,172 @@
/**
* @file fsposestate.h
* @brief a means to save and restore the instantaneous state of animations posing an avatar.
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (c) 2025 Angeldark Raymaker @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_FSPoseState_H
#define LL_FSPoseState_H
#include "llvoavatar.h"
#include "fsposingmotion.h"
class FSPoseState
{
public:
FSPoseState() = default;
virtual ~FSPoseState() = default;
public:
/// <summary>
/// Captures the current animations posing the supplied avatar and how long they have been playing.
/// </summary>
/// <param name="avatar">The avatar whose animations are to be captured.</param>
void captureMotionStates(LLVOAvatar* avatar);
/// <summary>
/// Updates the stored list of animations posing the avatar.
/// </summary>
/// <param name="avatar">The avatar whose animations are to be captured.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="jointNamesRecaptured">The names of the joints being recaptured.</param>
void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, std::string jointNamesRecaptured);
/// <summary>
/// Add a new posing state, or updates the matching posing state with the supplied data.
/// </summary>
/// <param name="avatar">The avatar the posing state is intended for.</param>
/// <param name="animId">The ID of the animation.</param>
/// <param name="updateTime">The frame-time of the animation.</param>
/// <param name="jointNames">The names of the joints, if any, the animation should specifically be applied to.</param>
/// <param name="captureOrder">The capture order.</param>
/// <returns>True if the posing state was added or changed by the update data, otherwise false.</returns>
bool addOrUpdatePosingMotionState(LLVOAvatar* avatar, LLUUID animId, F32 updateTime, std::string jointNames, int captureOrder);
/// <summary>
/// Removes all current animation states for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar whose animations are to be purged.</param>
void purgeMotionStates(LLVOAvatar* avatar);
/// <summary>
/// Writes any documented poses for the supplied avatar to the supplied stream.
/// </summary>
/// <param name="avatar">The avatar whose animations may have been captured.</param>
/// <param name="saveRecord">The record to add to.</param>
void writeMotionStates(LLVOAvatar* avatar, LLSD* saveRecord);
/// <summary>
/// Restores pose state(s) from the supplied record.
/// </summary>
/// <param name="avatar">The avatar whose animations may have been captured.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="pose">The record to read from.</param>
void restoreMotionStates(LLVOAvatar* avatar, LLSD pose);
/// <summary>
/// Applies the motion states for the supplied avatar to the supplied motion.
/// </summary>
/// <param name="avatar">The avatar to apply the motion state(s) to.</param>
/// <param name="posingMotion">The posing motion to apply the state(s) to.</param>
/// <returns>True if all the motion states for the supplied avatar have been applied, otherwise false.</returns>
/// <remarks>
/// In some ways this is like an AO: loading LLKeyframeMotions.
/// Once loaded, the LLKeyframeMotion is put at time fsMotionState.lastUpdateTime.
/// The joint-rotations for that LLKeyframeMotion are then restored to the base.
/// This examines sMotionStates for any avatarId matches; such as after a restoreMotionStates(...).
/// This could result in loading assets, thus a particular member of sMotionStates may take several attempts to load.
/// Motion(s) that the avatar does not have permissions for are not considered in the return boolean.
/// </remarks>
bool applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion);
private:
/// <summary>
/// A class documenting the state of an animation for an avatar.
/// </summary>
class fsMotionState
{
public:
/// <summary>
/// The motion ID recorded animating the avatar ID.
/// </summary>
LLAssetID motionId;
/// <summary>
/// The play-time the motionId had progressed until the motion was captured.
/// </summary>
F32 lastUpdateTime = 0.f;
/// <summary>
/// Upon reloading, whether this record has been applied to the avatar.
/// </summary>
bool motionApplied = false;
/// <summary>
/// Whether the avatar owns the pose, or the pose was loaded.
/// </summary>
bool avatarOwnsPose = false;
/// <summary>
/// When reloading, larger numbers are loaded last, nesting order and priority.
/// This is used to represent recaptures, where joints could be animated with different poses.
/// </summary>
int captureOrder = 0;
/// <summary>
/// When reloading, and if not-empty, the names of the bones this motionId should affect.
/// </summary>
std ::string jointNamesAnimated;
};
/// <summary>
/// Resets the priority for the named joints for the supplied posing motion at the supplied capture order.
/// </summary>
/// <param name="avatar">The avatar being posed by the motion.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="captureOrder">The order of the capture.</param>
void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, int captureOrder);
/// <summary>
/// Gets whether the supplied avatar owns, and thus can save information about the supplied asset ID.
/// </summary>
/// <param name="avatar">The avatar to query ownership for.</param>
/// <param name="motionId">The asset ID of the object.</param>
/// <returns>True if the avatar owns the asset, otherwise false.</returns>
bool canSaveMotionId(LLVOAvatar* avatar, LLAssetID motionId);
struct compareByCaptureOrder
{
bool operator()(const fsMotionState& a, const fsMotionState& b)
{
if (a.captureOrder < b.captureOrder)
return true; // Ascending order
return false;
}
};
static std::map <LLUUID, std::vector<fsMotionState>> sMotionStates;
static std::map<LLUUID, int> sCaptureOrder;
};
#endif // LL_FSPoseState_H

View File

@ -29,10 +29,11 @@
#include "fsposingmotion.h"
#include "llcharacter.h"
FSPosingMotion::FSPosingMotion(const LLUUID &id) : LLMotion(id)
FSPosingMotion::FSPosingMotion(const LLUUID& id) : LLKeyframeMotion(id)
{
mName = "fs_poser_pose";
mMotionID = id;
mJointMotionList = &dummyMotionList;
}
LLMotion::LLMotionInitStatus FSPosingMotion::onInitialize(LLCharacter *character)
@ -269,6 +270,113 @@ void FSPosingMotion::setJointBvhLock(FSJointPose* joint, bool lockInBvh)
joint->zeroBaseRotation(lockInBvh);
}
bool FSPosingMotion::loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames)
{
FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast<FSPosingMotion*>(motionToLoad);
if (!motionToLoadAsFsPosingMotion)
return false;
LLJoint::JointPriority priority = motionToLoad->getPriority();
bool motionIsForAllJoints = selectedJointNames.empty();
LLQuaternion rot;
LLVector3 position, scale;
bool hasRotation = false, hasPosition = false, hasScale = false;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
std::string jointName = poserJoint_iter->jointName();
bool motionIsForThisJoint = selectedJointNames.find(jointName) != std::string::npos;
if (!motionIsForAllJoints && !motionIsForThisJoint)
continue;
hasRotation = hasPosition = hasScale = false;
motionToLoadAsFsPosingMotion->getJointStateAtTime(jointName, timeToLoadAt, &hasRotation, &rot, &hasPosition, &position, &hasScale, &scale);
if (hasRotation)
poserJoint_iter->setBaseRotation(rot, priority);
if (hasPosition)
poserJoint_iter->setBasePosition(position, priority);
if (hasScale)
poserJoint_iter->setBaseScale(scale, priority);
}
return true;
}
void FSPosingMotion::getJointStateAtTime(std::string jointPoseName, F32 timeToLoadAt,
bool* hasRotation, LLQuaternion* jointRotation,
bool* hasPosition, LLVector3* jointPosition,
bool* hasScale, LLVector3* jointScale)
{
if ( mJointMotionList == nullptr)
return;
for (U32 i = 0; i < mJointMotionList->getNumJointMotions(); i++)
{
JointMotion* jm = mJointMotionList->getJointMotion(i);
if (!boost::iequals(jointPoseName, jm->mJointName))
continue;
*hasRotation = (jm->mRotationCurve.mNumKeys > 0);
if (hasRotation)
jointRotation->set(jm->mRotationCurve.getValue(timeToLoadAt, mJointMotionList->mDuration));
*hasPosition = (jm->mPositionCurve.mNumKeys > 0);
if (hasPosition)
jointPosition->set(jm->mPositionCurve.getValue(timeToLoadAt, mJointMotionList->mDuration));
*hasScale = (jm->mScaleCurve.mNumKeys > 0);
if (hasScale)
jointScale->set(jm->mScaleCurve.getValue(timeToLoadAt, mJointMotionList->mDuration));
return;
}
}
bool FSPosingMotion::otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames)
{
FSPosingMotion* motionToLoadAsFsPosingMotion = static_cast<FSPosingMotion*>(motionToQuery);
if (!motionToLoadAsFsPosingMotion)
return false;
return motionToLoadAsFsPosingMotion->motionAnimatesJoints(recapturedJointNames);
}
bool FSPosingMotion::motionAnimatesJoints(std::string recapturedJointNames)
{
if (mJointMotionList == nullptr)
return false;
for (U32 i = 0; i < mJointMotionList->getNumJointMotions(); i++)
{
JointMotion* jm = mJointMotionList->getJointMotion(i);
if (recapturedJointNames.find(jm->mJointName) == std::string::npos)
continue;
if (jm->mRotationCurve.mNumKeys > 0)
return true;
}
return false;
}
void FSPosingMotion::resetBonePriority(std::string boneNamesToReset)
{
if (boneNamesToReset.empty())
return;
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
std::string jointName = poserJoint_iter->jointName();
if (boneNamesToReset.find(jointName) != std::string::npos)
poserJoint_iter->setJointPriority(LLJoint::LOW_PRIORITY);
}
}
bool FSPosingMotion::vectorsNotQuiteEqual(LLVector3 v1, LLVector3 v2) const
{
if (vectorAxesAlmostEqual(v1.mV[VX], v2.mV[VX]) &&

View File

@ -32,6 +32,7 @@
//-----------------------------------------------------------------------------
#include "llmotion.h"
#include "fsjointpose.h"
#include "llkeyframemotion.h"
#define MIN_REQUIRED_PIXEL_AREA_POSING 500.f
@ -39,10 +40,11 @@
// class FSPosingMotion
//-----------------------------------------------------------------------------
class FSPosingMotion :
public LLMotion
public LLKeyframeMotion
{
public:
FSPosingMotion(const LLUUID &id);
FSPosingMotion(const LLKeyframeMotion& kfm) : LLKeyframeMotion{ kfm } { }
virtual ~FSPosingMotion(){};
public:
@ -132,6 +134,58 @@ public:
/// <param name="lockInBvh">Whether the joint should be locked if exported to BVH.</param>
void setJointBvhLock(FSJointPose* joint, bool lockInBvh);
/// <summary>
/// Loads the rotations of the supplied motion at the supplied time to the base
/// </summary>
/// <param name="motionToLoad">The motion whose joint rotations (etc) we want to copy to this.</param>
/// <param name="timeToLoadAt">The play-time the animation should be advanced to derive the correct joint state.</param>
/// <param name="selectedJointNames">If only some of the joints should be animated by this motion, name them here.</param>
/// <returns></returns>
bool loadOtherMotionToBaseOfThisMotion(LLKeyframeMotion* motionToLoad, F32 timeToLoadAt, std::string selectedJointNames);
/// <summary>
/// Tries to get the rotation, position and scale for the supplied joint name at the supplied time.
/// </summary>
/// <param name="jointPoseName">The name of the joint. Example: "mPelvis".</param>
/// <param name="timeToLoadAt">The time to get the rotation at.</param>
/// <param name="hasRotation">Output of whether the animation has a rotation for the supplied joint name.</param>
/// <param name="jointRotation">The output rotation of the named joint.</param>
/// <param name="hasPosition">Output of whether the animation has a position for the supplied joint name.</param>
/// <param name="jointPosition">The output position of the named joint.</param>
/// <param name="hasScale">Output of whether the animation has a scale for the supplied joint name.</param>
/// <param name="jointScale">The output scale of the named joint.</param>
/// <remarks>
/// The most significant thing this method does is provide access to protected properties of some other LLPosingMotion.
/// Thus its most common usage would be to access those properties for an arbitrary animation 'from' the poser's instance of one of these.
/// </remarks>
void getJointStateAtTime(std::string jointPoseName, F32 timeToLoadAt, bool* hasRotation, LLQuaternion* jointRotation,
bool* hasPosition, LLVector3* jointPosition, bool* hasScale, LLVector3* jointScale);
/// <summary>
/// Resets the bone priority to zero for the joints named in the supplied string.
/// </summary>
/// <param name="boneNamesToReset">The string containg bone names (like mPelvis).</param>
void resetBonePriority(std::string boneNamesToReset);
/// <summary>
/// Queries whether the supplied motion animates any of the joints named in the supplied string.
/// </summary>
/// <param name="motionToQuery">The motion to query.</param>
/// <param name="recapturedJointNames">A string containing all of the joint names.</param>
/// <returns>True if the motion animates any of the bones named, otherwise false.</returns>
bool otherMotionAnimatesJoints(LLKeyframeMotion* motionToQuery, std::string recapturedJointNames);
/// <summary>
/// Queries whether the this motion animates any of the joints named in the supplied string.
/// </summary>
/// <param name="recapturedJointNames">A string containing all of the joint names.</param>
/// <returns>True if the motion animates any of the bones named, otherwise false.</returns>
/// <remarks>
/// The most significant thing this method does is provide access to protected properties of an LLPosingMotion.
/// Thus its most common usage would be to access those properties for an arbitrary animation.
/// </remarks>
bool motionAnimatesJoints(std::string recapturedJointNames);
private:
/// <summary>
/// The axial difference considered close enough to be the same.
@ -154,6 +208,11 @@ private:
/// </summary>
LLAssetID mMotionID;
/// <summary>
/// Constructor and usage requires this not be NULL.
/// </summary>
JointMotionList dummyMotionList;
/// <summary>
/// The time constant, in seconds, we use for transitioning between one animation-state to another; this affects the 'damping'
/// of motion between changes to a joint. 'Constant' in this context is not a reference to the language-idea of 'const' value.

View File

@ -509,7 +509,7 @@ LLAgent::LLAgent() :
mMouselookModeInSignal(NULL),
mMouselookModeOutSignal(NULL),
mFSAreaSearchActive(false), // <FS:minerjr> - Flag was not initialized
mPhantom(false),
restoreToWorld(false)
{

View File

@ -9353,7 +9353,9 @@ void LLRecentItemsFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
buildContextMenuOptions(flags, items, disabled_items);
items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end());
items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end());
// <FS:TJ> [FIRE-35996] Restore allowing creating folder from selected on recent and favorites panels
//items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end());
// </FS:TJ>
hide_context_entries(menu, items, disabled_items);
}
@ -9398,7 +9400,9 @@ void LLFavoritesFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
buildContextMenuOptions(flags, items, disabled_items);
items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end());
items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end());
// <FS:TJ> [FIRE-35996] Restore allowing creating folder from selected on recent and favorites panels
//items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end());
// </FS:TJ>
hide_context_entries(menu, items, disabled_items);
}

View File

@ -38,6 +38,7 @@
#include "llagent.h"
#include "llagentcamera.h"
#include "llviewercamera.h"
#include "llviewercontrol.h" // <FS:PP> gSavedSettings
#include "llviewertexture.h"
#include "llviewertexturelist.h"
#include "llviewerwindow.h"
@ -568,6 +569,12 @@ void LLJoystickCameraRotate::onHeldDown()
void LLJoystickCameraRotate::resetJoystickCamera()
{
// <FS:PP> If user opted to disable center reset buttons, do not reset
if (gSavedSettings.getBOOL("DisableCameraJoystickCenterReset"))
{
return;
}
// </FS:PP>
gAgentCamera.resetCameraOrbit();
}
@ -735,6 +742,12 @@ void LLJoystickCameraTrack::onHeldDown()
void LLJoystickCameraTrack::resetJoystickCamera()
{
// <FS:PP> If user opted to disable center reset buttons, do not reset
if (gSavedSettings.getBOOL("DisableCameraJoystickCenterReset"))
{
return;
}
// </FS:PP>
gAgentCamera.resetCameraPan();
}

View File

@ -1171,7 +1171,7 @@ void LLOutfitListBase::onIdleRefreshList()
F64 MAX_TIME = 0.05f;
constexpr F64 min_time = 0.001f;
constexpr F64 threshold_fps = 30.0;
const auto current_fps = LLTrace::get_frame_recording().getPeriodMedianPerSec(LLStatViewer::FPS, 1);
const auto current_fps = LLTrace::get_frame_recording().getPeriodMedianPerSec(LLStatViewer::FPS,10);
if (current_fps < threshold_fps)
{
MAX_TIME = min_time + (current_fps / threshold_fps) * (MAX_TIME - min_time);

View File

@ -429,7 +429,10 @@ void LLPanelOutfitsInventory::onTabChange()
}
if (mTrashMenuPanel)
{
mTrashMenuPanel->setVisible(mActivePanel->getTrashMenuVisible());
// <FS:PP> FIRE-35947 Ensure the top menu buttons (gear/sort/trash) are only visible in the outfits panel
// mTrashMenuPanel->setVisible(mActivePanel->getTrashMenuVisible());
mTrashMenuPanel->setVisible(false);
// </FS:PP>
}
updateVerbs();

View File

@ -366,9 +366,9 @@ void LLSidepanelAppearance::toggleMyOutfitsPanel(bool visible, const std::string
mCurrOutfitPanel->setVisible(visible);
// <FS:PP> FIRE-35947 Ensure the top menu buttons (gear/sort/trash) are only visible in the outfits panel
getChildView("options_gear_btn_panel")->setVisible(visible);
getChildView("options_gear_btn_panel")->setVisible(false);
getChildView("options_sort_btn_panel")->setVisible(visible);
getChildView("trash_btn_panel")->setVisible(visible);
getChildView("trash_btn_panel")->setVisible(false);
// </FS:PP>
if (visible)

View File

@ -2095,6 +2095,30 @@ LLPointer<LLImageRaw> LLTextureCache::readFromFastCache(const LLUUID& id, S32& d
}
LLPointer<LLImageRaw> raw = new LLImageRaw(data, head[0], head[1], head[2], true);
// <FS:minerjr>
// This fixes the invalid discard values from being created which cause the decoder code to fail when trying to handle 6 and 7's which are above the MAX_DISCARD_LEVEL of 5
// especially on load
// We will expand the 16x16 texture to the actual MAX_DISCARD_LEVEL texture size, it may be blurry until the user gets closer but 5 discard value should be objects far from the camera.
// So a 1024x1024 texture with a dicard of 6 will become 32x32 and a 2048x2048 texture with a discard of 7 will become a 64x64 texture.
if (discardlevel > MAX_DISCARD_LEVEL)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("FixBadDiscardLevel");
S32 w = head[0]; // Get the current width from the header (16)
S32 h = head[1]; // Get the current height from the header (16)
// Expand the width and height by teh difference between the discard and MAX_DISCARD_LEVEL bit shifted to the left. (Expand power of 2 textures)
w <<= discardlevel - MAX_DISCARD_LEVEL;
h <<= discardlevel - MAX_DISCARD_LEVEL;
// Set the discard level to the MAX_DISCARD_LEVEL
discardlevel = MAX_DISCARD_LEVEL;
// Scale up the texture and scale the actual data, as we just created it above, it should be fine.
raw->scale(w, h, true);
}
// </FS:minerjr>
return raw;
}

View File

@ -1899,10 +1899,10 @@ bool LLTextureFetchWorker::doWork(S32 param)
mHttpReplyOffset = 0;
mLoadedDiscard = mRequestedDiscard;
if (mLoadedDiscard < 0)
if (mLoadedDiscard < 0 || (mLoadedDiscard > MAX_DISCARD_LEVEL && mFormattedImage->getCodec() == IMG_CODEC_J2C))
{
LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard
<< ", should be >=0" << LL_ENDL;
<< ", should be >=0 and <=" << MAX_DISCARD_LEVEL << LL_ENDL;
}
setState(DECODE_IMAGE);
if (mWriteToCacheState != NOT_WRITE)
@ -1964,14 +1964,27 @@ bool LLTextureFetchWorker::doWork(S32 param)
LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: mLoadedDiscard < 0" << LL_ENDL;
return true;
}
llassert_always(mFormattedImage.notNull());
S32 discard = mHaveAllData && mFormattedImage->getCodec() != IMG_CODEC_J2C ? 0 : mLoadedDiscard;
if (discard > MAX_DISCARD_LEVEL) // only warn for j2c
{
// We encode j2c with fixed amount of discard levels,
// Trying to decode beyound that will fail.
LL_WARNS(LOG_TXT) << "Decode entered with invalid discard. ID = " << mID << LL_ENDL;
//abort, don't decode
setState(DONE);
LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: mLoadedDiscard > MAX_DISCARD_LEVEL" << LL_ENDL;
return true;
}
mDecodeTimer.reset();
mRawImage = NULL;
mAuxImage = NULL;
llassert_always(mFormattedImage.notNull());
// if we have the entire image data (and the image is not J2C), decode the full res image
// DO NOT decode a higher res j2c than was requested. This is a waste of time and memory.
S32 discard = mHaveAllData && mFormattedImage->getCodec() != IMG_CODEC_J2C ? 0 : mLoadedDiscard;
mDecoded = false;
setState(DECODE_IMAGE_UPDATE);
LL_DEBUGS(LOG_TXT) << mID << ": Decoding. Bytes: " << mFormattedImage->getDataSize() << " Discard: " << discard

View File

@ -6587,6 +6587,24 @@ void LLViewerObject::setAttachedSound(const LLUUID &audio_uuid, const LLUUID& ow
return;
}
// <FS:Ansariel> Asset blacklist
FSAssetBlacklist& blacklist = FSAssetBlacklist::instance();
if (blacklist.isBlacklisted(audio_uuid, LLAssetType::AT_SOUND))
{
return;
}
else if (isAttachment() && blacklist.isBlacklisted(owner_id, LLAssetType::AT_SOUND, FSAssetBlacklist::eBlacklistFlag::WORN))
{
// Attachment sound
return;
}
else if (blacklist.isBlacklisted(owner_id, LLAssetType::AT_SOUND, FSAssetBlacklist::eBlacklistFlag::REZZED))
{
// Rezzed object sound
return;
}
// </FS:Ansariel>
if (flags & LL_SOUND_FLAG_LOOP
&& mAudioSourcep && mAudioSourcep->isLoop() && mAudioSourcep->getCurrentData()
&& mAudioSourcep->getCurrentData()->getID() == audio_uuid)

View File

@ -2229,7 +2229,7 @@ bool LLViewerFetchedTexture::updateFetch()
static LLCachedControl<U32> sTextureDiscardLevel(gSavedSettings, "TextureDiscardLevel");
const U32 override_tex_discard_level = sTextureDiscardLevel();
// </FS:Ansariel>
if (override_tex_discard_level != 0)
if (override_tex_discard_level != 0 && override_tex_discard_level <= MAX_DISCARD_LEVEL)
{
desired_discard = override_tex_discard_level;
}

View File

@ -515,7 +515,7 @@ void LLVOCacheEntry::updateDebugSettings()
sNearRadius = MIN_RADIUS + ((clamped_min_radius - MIN_RADIUS) * adjust_factor);
// a percentage of draw distance beyond which all objects outside of view frustum will be unloaded, regardless of pixel threshold
static LLCachedControl<F32> rear_max_radius_frac(gSavedSettings,"SceneLoadRearMaxRadiusFraction");
static LLCachedControl<F32> rear_max_radius_frac(gSavedSettings,"SceneLoadRearMaxRadiusFraction", .75f);
const F32 min_radius_plus_one = sNearRadius + 1.f;
const F32 max_radius = rear_max_radius_frac * draw_radius;
const F32 clamped_max_radius = llclamp(max_radius, min_radius_plus_one, draw_radius); // [sNearRadius, mDrawDistance]

View File

@ -8603,7 +8603,7 @@ void LLPipeline::renderFinalize()
};
// </FS:Beq>
// <FS:Beq> new shader for snapshot frame helper
if (renderSnapshotFrame(auxTargetBuffer, auxActiveBuffer))
if (renderSnapshotFrame(auxActiveBuffer, auxTargetBuffer))
{
std::swap(auxActiveBuffer, auxTargetBuffer);
};

View File

@ -151,6 +151,7 @@
<check_box label="„Freunde“ und „Gruppen“ im Menü „Unterhalten“ öffnen die [SHORT_VIEWER_GENERATION]-Version" name="FSUseV2Friends" tool_tip="Falls aktiviert, werden Freunde bzw. Gruppen im „Leute“-Fenster geöffnet - ansonsten im Fenster „Kontakte“."/>
<check_box label="Ursprüngliche Version des Objekteigenschaften-Fensters verwenden" name="FSUseLegacyObjectProperties" tool_tip="Falls aktiviert, wird die ursprüngliche Version im Viewer-1-Stil des Eigenschaften-Fensters für Objekte verwendet."/>
<check_box label="Kleineres Fenster für Kamerasteuerung verwenden" name="FSUseSmallCameraFloater" tool_tip="Falls aktiviert, wird die ursprüngliche, kleinere Version des Fensters für die Kamerasteuerung ohne die Kontrollen für die Kamera-Voreinstellungen verwendet."/>
<check_box label="Zurücksetzbuttons für Kamerasteuerung deaktivieren" name="DisableCameraJoystickCenterReset" tool_tip="Verhindert, dass die Mitte der Orbit- und Bewegungssteuerung die Kamera zurücksetzen."/>
<text name="standalone_textbox">
Eigene Fenster verwenden für:
</text>

View File

@ -1838,6 +1838,15 @@ width="430">
<button.commit_callback
function="Poser.BrowseCache"/>
</button>
<loading_indicator
visible="false"
follows="left|top"
height="20"
layout="topleft"
name="progress_indicator"
left_pad="1"
top_delta="0"
width="20" />
<menu_button
height="21"
follows="top|left"
@ -1856,7 +1865,7 @@ width="430">
name="load_poses_button"
left_pad="1"
top_delta="0"
width="95"/>
width="85"/>
<button
height="21"
follows="top|left"
@ -1871,7 +1880,7 @@ width="430">
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
name="save_poses_button"
width="95"
width="85"
top_delta="0"
left_pad="1">
<button.commit_callback

View File

@ -989,6 +989,15 @@
width="270"
control_name="FSUseSmallCameraFloater"
tool_tip="If enabled, the smaller, legacy camera window without camera preset controls will be used."/>
<check_box
top_pad="0"
follows="left|top"
height="16"
label="Disable camera controls reset buttons"
name="DisableCameraJoystickCenterReset"
width="270"
control_name="DisableCameraJoystickCenterReset"
tool_tip="Prevents the center bullseye of the orbit and move camera controls from resetting the camera."/>
<text
type="string"

View File

@ -18,7 +18,7 @@
<slider label="赤" name="wmiColorFilterBaseR"/>
<slider label="緑" name="wmiColorFilterBaseG"/>
<slider label="青" name="wmiColorFilterBaseB"/>
<slider label="" name="wmiColorFilterBaseI"/>
<slider label="" name="wmiColorFilterBaseI"/>
</panel>
<panel label="暗視" name="wmiNightVisionPanel">
<check_box label="有効" name="wmiNightVisionToggle"/>

View File

@ -50,9 +50,9 @@
<menu_item_check label="移動操作" name="Movement Controls"/>
<menu_item_check label="カメラ操作" name="Camera Controls"/>
<menu label="アバターの状態" name="avhealth">
<menu_item_call label="自分のアニメーションを停止する" name="Stop Animating My Avatar"/>
<menu_item_call label="アニメーションを停止&権限を取り消し" name="Stop Animating My Avatar With Revoke"/>
<menu_item_call label="自分を変形しない" name="undeform_avatar"/>
<menu_item_call label="アバターのアニメーションを停止する" name="Stop Animating My Avatar"/>
<menu_item_call label="アバターのアニメーションを停止&権限を取り消す" name="Stop Animating My Avatar With Revoke"/>
<menu_item_call label="アバターの変形を解除" name="undeform_avatar"/>
<menu_item_call label="スケルトンをリセット" name="Reset Skeleton"/>
<menu_item_call label="スケルトンとアニメーションのリセット" name="Reset Skeleton And Animations"/>
<menu_item_call label="テクスチャをリベークする" name="Rebake Texture"/>
@ -141,11 +141,11 @@
<menu_item_check label="グラフィック性能の最適化…" name="Performance"/>
<menu_item_call label="ホームにテレポート" name="Teleport Home"/>
<menu label="自然環境" name="Environment">
<menu_item_check label="夜明け" name="Sunrise"/>
<menu_item_check label="正午" name="Noon"/>
<menu_item_check label="正午(レガシー)" name="legacy noon"/>
<menu_item_check label="朝方" name="Sunrise"/>
<menu_item_check label="昼間" name="Noon"/>
<menu_item_check label="昼間(レガシー)" name="legacy noon"/>
<menu_item_check label="夕方" name="Sunset"/>
<menu_item_check label="夜" name="Midnight"/>
<menu_item_check label="" name="Midnight"/>
<menu_item_check label="共有された環境を使用" name="Use Shared Environment"/>
<menu_item_call label="自分の環境…" name="my_environs"/>
<menu_item_call label="環境の個人設定…" name="adjustment_tool"/>

View File

@ -5515,14 +5515,14 @@ Firestorm Viewer、Phoenix Firestorm Viewer Project Inc.、またはそのチー
圧縮:[PACK_TIME]秒 [PSIZE]㎅
解凍:[UNPACK_TIME]秒 [USIZE]㎅
</notification>
<notification label="段階認証トークンプロンプト" name="PromptMFAToken">
<notification label="段階認証トークンプロンプト" name="PromptMFAToken">
[MESSAGE]
<form name="form">
<button name="continue" text="確認"/>
<button name="cancel" text="キャンセル"/>
</form>
</notification>
<notification label="段階認証トークンプロンプト" name="PromptMFATokenWithSave">
<notification label="段階認証トークンプロンプト" name="PromptMFATokenWithSave">
[MESSAGE]
<form name="form">
<ignore name="ignore" text="このコンピューターを30日間記録する"/>
@ -5543,12 +5543,12 @@ Firestorm Viewer、Phoenix Firestorm Viewer Project Inc.、またはそのチー
<usetemplate name="okbutton" yestext=""/>
</notification>
<notification name="RiggedMeshAttachedToHUD">
HUDポイント「[POINT]」にアタッチされたオブジェクト「[NAME]」にはリグメッシュが含まれています。
HUDポイント「[POINT]」に装着されたオブジェクト「[NAME]」にはリグメッシュが含まれています。
リグメッシュオブジェクトは、アバターに取り付けるように設計されています。あなたにはこのオブジェクトが表示されますが、他の人には表示されません。
他の人にこのオブジェクトを見てもらいたい場合は、オブジェクトを削除して、アバターのアタッチメントポイントに再度アタッチします。
<usetemplate ignoretext="リグメッシュがHUDポイントにアタッチされている場合の警告" name="okignore" yestext=""/>
他の人にこのオブジェクトを見てもらいたい場合は、オブジェクトを削除して、アバターの装着ポイントに再度装着します。
<usetemplate ignoretext="リグメッシュがHUDポイントに装着されている場合の警告" name="okignore" yestext=""/>
</notification>
<notification name="ConfirmOverwriteOutfit">
これにより、選択したアウトフィットのアイテムが現在着ているアイテムに置き換えられます。
@ -5642,7 +5642,7 @@ Firestorm Viewer、Phoenix Firestorm Viewer Project Inc.、またはそのチー
</notification>
<!-- <FS:Zi> detect and strip empty alpha layers from images on upload -->
<notification label="空のアルファチャンネルが含まれた画像" name="ImageEmptyAlphaLayer">
アップロードしようとしている画像には、空またはほぼ空のアルファチャンネル(透情報)が含まれています。これはほとんどの場合望ましくないため、削除する必要があります。画像にアルファチャンネルを追加すると、異なるカメラ角度でテクスチャが互いに反転し、描画が遅くなります。したがって、このテクスチャに空またはほぼ空のアルファチャンネルが本当に必要な場合を除き、削除することを検討してください。
アップロードしようとしている画像には、空またはほぼ空のアルファチャンネル(透情報)が含まれています。これはほとんどの場合望ましくないため、削除する必要があります。画像にアルファチャンネルを追加すると、異なるカメラ角度でテクスチャが互いに反転し、描画が遅くなります。したがって、このテクスチャに空またはほぼ空のアルファチャンネルが本当に必要な場合を除き、削除することを検討してください。
<form name="form">
<button name="strip" text="アルファチャンネルを削除"/>
<button name="use_as_is" text="このまま使用"/>

View File

@ -156,6 +156,7 @@
<check_box label="「コミュニケーション」メニューの「フレンド」と「所属グループ」を[SHORT_VIEWER_GENERATION]形式のウィンドウで開く" name="FSUseV2Friends" tool_tip="ここにチェックを入れると、「フレンド」や「所属グループ」を「人物」パネルで開くようになります。チェックを入れない場合は、「連絡先」パネルで開きます。"/>
<check_box label="古いタイプのオブジェクトプロパティを使用" name="FSUseLegacyObjectProperties" tool_tip="ここにチェックを入れると、オブジェクトのプロパティを開く時に、古いタイプのオブジェクト・プロパティが使用されます。"/>
<check_box label="小さいカメラウィンドウを使用" name="FSUseSmallCameraFloater" tool_tip="ここにチェックを入れると、クラシックなプリセットコントロールのない小さなカメラウィンドウが利用できるようになります。"/>
<check_box label="カメラコントロールのリセットボタンを無効化する" name="DisableCameraJoystickCenterReset" tool_tip="軌道の中心を狙い、カメラコントロールを移動してもカメラがリセットされないようにします。"/>
<text name="standalone_textbox">
次の項目に独立したウィンドウを使用:
</text>

View File

@ -332,7 +332,7 @@ https://secondlife.com/viewer-access-faq
しばらくしてから再度お試しください。
</string>
<string name="LoginFailedAuthenticationMFARequired">
ログインするには段階認証アプリから新しいトークンを入力する必要があります。
ログインするには段階認証アプリから新しいトークンを入力する必要があります。
それでもこのエラーが発生する場合は、support@secondlife.comにお問い合わせください。
</string>
<!-- Disconnection -->
@ -5876,7 +5876,7 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ
<string name="Command_Group_Titles_Tooltip">アクティブなグループタグを変更します。</string>
<string name="Command_Wearable_Favorites_Tooltip">お気に入りの着用物のリストを開きます。</string>
<string name="Command_RFO_Tooltip">ビューアにはフレンドのアバターのみが表示され、他のすべてのアバターは削除されます。有効にすると、他のユーザを見えるようにするTPが必要になります。</string>
<string name="Command_DAO_Tooltip">アニメーションオブジェクトの描画解除(別名アニメッシュ)-現在表示されているすべてのアニメッシュ(アタッチされているもの、フリーローミングのもの)を一時的に描画解除します。描画解除されたアニメッシュは、TP後に再び表示されます。</string>
<string name="Command_DAO_Tooltip">アニメーションオブジェクトの描画解除(別名アニメッシュ)-現在表示されているすべてのアニメッシュ(装着されているもの、フリーローミングのもの)を一時的に描画解除します。描画解除されたアニメッシュは、TP後に再び表示されます。</string>
<string name="Command_Beacons_Tooltip">ビーコンを表示します。</string>
<string name="Toolbar_Bottom_Tooltip">
現在、下部のツールバー(↓)にあります。
@ -5893,10 +5893,10 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ
</string>
<!-- added Jun.30, 2013 -->
<string name="Command_Stop_Animations_Label">
自分のアニメーション停止する
アニメーション停止
</string>
<string name="Command_Stop_Animations_Tooltip">
自分のアニメーションを停止する
自分のアバターのアニメーションを停止させます。
</string>
<!-- Mesh UI terms -->
<string name="Retain%">

View File

@ -143,6 +143,7 @@
<check_box label="'Znajomi' oraz 'Grupy' w menu 'Komunikacja' otwierają okna w stylu [SHORT_VIEWER_GENERATION]" name="FSUseV2Friends" tool_tip="Gdy włączysz tą opcję, to Znajomi / Grupy będą się otwierać poprzez okno Ludzie - w przeciwnym wypadku będzie się to odbywać w oknie Kontaktów." />
<check_box label="Stary sposób wyświetlania właściwości obiektu" name="FSUseLegacyObjectProperties" tool_tip="Gdy włączysz tą opcję, to stare okno właściwości obiektu będzie używane zamiast nowego."/>
<check_box label="Użyj małego okna kamery" name="FSUseSmallCameraFloater" tool_tip="Jeśli ta opcja jest włączona, to zostanie użyte mniejsze, starsze okno kamery bez elementów sterujących wstępnymi ustawieniami." />
<check_box label="Wyłącz przyciski resetowania kamery" name="DisableCameraJoystickCenterReset" tool_tip="Zapobiega resetowaniu kamery po kliknięciu w środkowy celownik (bullseye) w kontrolkach obrotu i ruchu kamery." />
<text name="standalone_textbox">
Używaj osobnych okien dla:
</text>

View File

@ -5667,6 +5667,11 @@ https://wiki.firestormviewer.org/antivirus_whitelisting
<usetemplate name="okcancelbuttons" notext="Отмена" yestext="Да"/>
</notification>
<notification name="FSLargeOutfitsWarningInThisSession">
Обнаружено большое количество комплектов: [AMOUNT]. Это может привести к зависанию или отключению устройства. Для повышения производительности рекомендуется уменьшить количество комплектов (ниже [MAX]).
<usetemplate ignoretext="Предупреждение о количестве комплектов" name="okignore" yestext="OK" />
</notification>
<notification name="PrimfeedLoginRequestFailed">
Запрос на вход отклонен Primfeed.
</notification>

View File

@ -152,6 +152,7 @@
<check_box label="Открывать окна в стиле [SHORT_VIEWER_GENERATION] для 'Друзья' и 'Группы'" name="FSUseV2Friends" tool_tip="Когда вы включите эту опцию, друзья / группы будут открыты в окнах - в противном случае, это будет сделано в Контактах." />
<check_box label="Использовать устаревшие окно свойства объектов" name="FSUseLegacyObjectProperties" tool_tip="Если включено, при открытии свойств объекта будет использоваться старое окно свойств."/>
<check_box label="Использовать маленькое окно камеры" name="FSUseSmallCameraFloater" tool_tip="Если этот параметр включен, будет использоваться меньшее, устаревшее окно камеры без элементов управления предустановками камеры."/>
<check_box label="Отключить кнопки сброса управления камерой" name="DisableCameraJoystickCenterReset" tool_tip="Предотвращает сброс настроек камеры с помощью центрального «яблочка» орбиты и перемещения элементов управления камерой."/>
<text name="standalone_textbox">
Использовать отдельные окна для:
</text>