From cd8e8570a0efa5c2a47999e6b85a78609e332ae1 Mon Sep 17 00:00:00 2001 From: Trish Date: Tue, 1 Sep 2026 20:45:27 -0400 Subject: [PATCH 1/7] FIX - Keep moving linkset children aligned with focused root --- indra/newview/llagentcamera.cpp | 68 ++++++++++++++++++++++++++++++++- indra/newview/llvoavatar.cpp | 10 ++++- indra/newview/llvoavatar.h | 2 +- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index d884b32ac04..a0b82c7556b 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -1651,6 +1651,73 @@ LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() gPipeline.updateMoveDampedAsync(drawablep); } } + + // Updating only a moving linkset root leaves its children on the normal + // update pass, which makes them appear dislocated from the root every few + // frames when the root was updated first. + // This is particularly noticeable on moving children or avatars (and avatar attachments). + if (mFocusObject->isRoot()) + { + for (LLViewerObject* childp : mFocusObject->getChildren()) + { + LLDrawable* child_drawablep = childp ? childp->mDrawable.get() : nullptr; + if (!child_drawablep || child_drawablep->isDead() || !child_drawablep->isActive()) + { + continue; + } + + child_drawablep->clearState(LLDrawable::EARLY_MOVE); + + if (childp->isSelected() || + child_drawablep->isState(LLDrawable::MOVE_UNDAMPED) || + !childp->getAngularVelocity().isExactlyZero()) + { + gPipeline.updateMoveNormalAsync(child_drawablep); + } + else + { + gPipeline.updateMoveDampedAsync(child_drawablep); + } + + // Also apply transform to any seated avatars + if (LLVOAvatar* avatarp = childp->asAvatar()) + { + if (LLJoint* root_jointp = avatarp->getRootJoint()) + { + root_jointp->touch(); + root_jointp->updateWorldMatrixChildren(); + const LLVector3 hud_name_pos = + avatarp->idleCalcNameTagPosition(root_jointp->getWorldPosition()); + avatarp->idleUpdateNameTag(hud_name_pos); + avatarp->idleUpdateVoiceVisualizerPosition(hud_name_pos); + } + + // ... and also the avatar's unrigged attachments (those are not + // children of the vehicle, they are children of the avatar) + for (const auto& attachment_entry : avatarp->mAttachmentPoints) + { + LLViewerJointAttachment* attachment = attachment_entry.second; + if (!attachment) + { + continue; + } + + for (const auto& attached_objectp : attachment->mAttachedObjects) + { + LLViewerObject* attached_object = attached_objectp.get(); + if (!attached_object || attached_object->isDead() || attached_object->mDrawable.isNull()) + { + continue; + } + + attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); + gPipeline.updateMoveNormalAsync(attached_object->mDrawable); + attached_object->updateText(); + } + } + } + } + } } } // if not tracking object, update offset based on new object position @@ -2948,4 +3015,3 @@ S32 LLAgentCamera::directionToKey(S32 direction) // EOF - diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index de8032ac40c..57090efe761 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -3015,10 +3015,18 @@ void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 & } } } - mVoiceVisualizer->setPositionAgent(position); + idleUpdateVoiceVisualizerPosition(position); }//if ( voiceEnabled ) } +void LLVOAvatar::idleUpdateVoiceVisualizerPosition(const LLVector3 &position) +{ + if (mVoiceVisualizer) + { + mVoiceVisualizer->setPositionAgent(position); + } +} + static void override_bbox(LLDrawable* drawable, LLVector4a* extents) { LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 37a7be7efbd..4cf0cb2a9e3 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -283,6 +283,7 @@ class LLVOAvatar : void updateRootPositionAndRotation(LLAgent &agent, F32 speed, bool was_sit_ground_constrained); void idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position); + void idleUpdateVoiceVisualizerPosition(const LLVector3 &position); void idleUpdateMisc(bool detailed_update); virtual void idleUpdateAppearanceAnimation(); void idleUpdateLipSync(bool voice_enabled); @@ -1355,4 +1356,3 @@ void dump_sequential_xml(const std::string outprefix, const LLSD& content); void dump_visual_param(apr_file_t* file, LLVisualParam* viewer_param, F32 value); #endif // LL_VOAVATAR_H - From cdf7bf8ecbbc38804b1eec46e69ad00034841ac1 Mon Sep 17 00:00:00 2001 From: trish-sl Date: Wed, 2 Sep 2026 16:42:48 -0400 Subject: [PATCH 2/7] Refactor attachment iteration for avatars Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- indra/newview/llagentcamera.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index a0b82c7556b..1a9ce54716b 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -1694,25 +1694,27 @@ LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() // ... and also the avatar's unrigged attachments (those are not // children of the vehicle, they are children of the avatar) - for (const auto& attachment_entry : avatarp->mAttachmentPoints) + for (LLVOAvatar::attachment_map_t::iterator iter = avatarp->mAttachmentPoints.begin(); + iter != avatarp->mAttachmentPoints.end(); ) { - LLViewerJointAttachment* attachment = attachment_entry.second; + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; if (!attachment) { continue; } - for (const auto& attached_objectp : attachment->mAttachedObjects) + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) { - LLViewerObject* attached_object = attached_objectp.get(); - if (!attached_object || attached_object->isDead() || attached_object->mDrawable.isNull()) + LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull()) { - continue; + attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); + gPipeline.updateMoveNormalAsync(attached_object->mDrawable); + attached_object->updateText(); } - - attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); - gPipeline.updateMoveNormalAsync(attached_object->mDrawable); - attached_object->updateText(); } } } From a92d1e62128785b47d3aaff9f5f3030e29f62fb4 Mon Sep 17 00:00:00 2001 From: Trish Date: Sat, 5 Sep 2026 22:39:07 -0400 Subject: [PATCH 3/7] Fix refactor & EARLY_MOVE aware changes --- indra/newview/llagentcamera.cpp | 190 ++++++++++++++++--------------- indra/newview/llviewerobject.cpp | 4 +- 2 files changed, 100 insertions(+), 94 deletions(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 1a9ce54716b..9ae7eaa0ade 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -106,6 +106,100 @@ static bool isDisableCameraConstraints() return sDisableCameraConstraints; } +// Keep the focused linkset's render state in step with the focused root without +// replaying movement that the normal pipeline has already processed this frame. +static void updateFocusedLinksetObject(LLViewerObject* objectp) +{ + if (!objectp || objectp->isDead()) + { + return; + } + + LLDrawable* drawablep = objectp->mDrawable.get(); + bool movement_updated = false; + if (drawablep && drawablep->isActive()) + { + if (!drawablep->isState(LLDrawable::EARLY_MOVE)) + { + if (objectp->isSelected() || + drawablep->isState(LLDrawable::MOVE_UNDAMPED) || + !objectp->getAngularVelocity().isExactlyZero()) + { + gPipeline.updateMoveNormalAsync(drawablep); + } + else + { + gPipeline.updateMoveDampedAsync(drawablep); + } + movement_updated = true; + } + } + + if (LLVOAvatar* avatarp = objectp->asAvatar()) + { + if (movement_updated) + { + if (LLJoint* root_jointp = avatarp->getRootJoint()) + { + root_jointp->touch(); + root_jointp->updateWorldMatrixChildren(); + const LLVector3 hud_name_pos = + avatarp->idleCalcNameTagPosition(root_jointp->getWorldPosition()); + avatarp->idleUpdateNameTag(hud_name_pos); + avatarp->idleUpdateVoiceVisualizerPosition(hud_name_pos); + } + + // Attachments have mixed movement sources. Preserve the same policy used by the normal avatar update path. + for (LLVOAvatar::attachment_map_t::iterator iter = avatarp->mAttachmentPoints.begin(); + iter != avatarp->mAttachmentPoints.end(); ) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + if (!attachment) + { + continue; + } + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (!attached_object || attached_object->isDead() || + attached_object->mDrawable.isNull()) + { + continue; + } + + LLDrawable* attached_drawablep = attached_object->mDrawable.get(); + if (!attached_drawablep->isActive()) + { + continue; + } + + if (!attached_drawablep->isState(LLDrawable::EARLY_MOVE)) + { + if (attached_object->isSelected()) + { + gPipeline.updateMoveNormalAsync(attached_drawablep); + } + else + { + gPipeline.updateMoveDampedAsync(attached_drawablep); + } + } + } + } + } + } + + // Seated avatars and sit-target children can be deeper than one level in the hierarchy. + for (LLViewerObject* childp : objectp->getChildren()) + { + updateFocusedLinksetObject(childp); + } +} + // The agent instance. LLAgentCamera gAgentCamera; @@ -1628,102 +1722,14 @@ LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() { if (mFocusObject.notNull() && !mFocusObject->isDead() && mFocusObject->mDrawable.notNull()) { - LLDrawable* drawablep = mFocusObject->mDrawable; - if (mTrackFocusObject && - drawablep && - drawablep->isActive()) + mFocusObject->mDrawable->isActive() && + !mFocusObject->isAvatar()) { - if (!mFocusObject->isAvatar()) - { - if (mFocusObject->isSelected()) - { - gPipeline.updateMoveNormalAsync(drawablep); - } - else - { - if (drawablep->isState(LLDrawable::MOVE_UNDAMPED)) - { - gPipeline.updateMoveNormalAsync(drawablep); - } - else - { - gPipeline.updateMoveDampedAsync(drawablep); - } - } - - // Updating only a moving linkset root leaves its children on the normal - // update pass, which makes them appear dislocated from the root every few - // frames when the root was updated first. - // This is particularly noticeable on moving children or avatars (and avatar attachments). - if (mFocusObject->isRoot()) - { - for (LLViewerObject* childp : mFocusObject->getChildren()) - { - LLDrawable* child_drawablep = childp ? childp->mDrawable.get() : nullptr; - if (!child_drawablep || child_drawablep->isDead() || !child_drawablep->isActive()) - { - continue; - } - - child_drawablep->clearState(LLDrawable::EARLY_MOVE); - - if (childp->isSelected() || - child_drawablep->isState(LLDrawable::MOVE_UNDAMPED) || - !childp->getAngularVelocity().isExactlyZero()) - { - gPipeline.updateMoveNormalAsync(child_drawablep); - } - else - { - gPipeline.updateMoveDampedAsync(child_drawablep); - } - - // Also apply transform to any seated avatars - if (LLVOAvatar* avatarp = childp->asAvatar()) - { - if (LLJoint* root_jointp = avatarp->getRootJoint()) - { - root_jointp->touch(); - root_jointp->updateWorldMatrixChildren(); - const LLVector3 hud_name_pos = - avatarp->idleCalcNameTagPosition(root_jointp->getWorldPosition()); - avatarp->idleUpdateNameTag(hud_name_pos); - avatarp->idleUpdateVoiceVisualizerPosition(hud_name_pos); - } - - // ... and also the avatar's unrigged attachments (those are not - // children of the vehicle, they are children of the avatar) - for (LLVOAvatar::attachment_map_t::iterator iter = avatarp->mAttachmentPoints.begin(); - iter != avatarp->mAttachmentPoints.end(); ) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - if (!attachment) - { - continue; - } - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull()) - { - attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); - gPipeline.updateMoveNormalAsync(attached_object->mDrawable); - attached_object->updateText(); - } - } - } - } - } - } - } + updateFocusedLinksetObject(mFocusObject.get()); } // if not tracking object, update offset based on new object position - else + else if (!mTrackFocusObject) { updateFocusOffset(); } diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index b51a63a0685..0fc7ee8c91f 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -4795,7 +4795,8 @@ const LLVector3 LLViewerObject::getRenderPosition() const } } - if (mDrawable.isNull() || mDrawable->getGeneration() < 0) + if (mDrawable.isNull() || + (!mDrawable->isActive() && mDrawable->getGeneration() < 0)) { return getPositionAgent(); } @@ -8036,4 +8037,3 @@ class ObjectPhysicsProperties : public LLHTTPNode LLHTTPRegistration gHTTPRegistrationObjectPhysicsProperties("/message/ObjectPhysicsProperties"); - From f2502b3c1852cef0da802ac2d90df32027559a78 Mon Sep 17 00:00:00 2001 From: trish-sl Date: Wed, 9 Sep 2026 13:53:40 -0400 Subject: [PATCH 4/7] Add updateMoveNormalAsync call for spatial bridge Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- indra/newview/llagentcamera.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 9ae7eaa0ade..a4704f7640e 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -187,6 +187,11 @@ static void updateFocusedLinksetObject(LLViewerObject* objectp) { gPipeline.updateMoveDampedAsync(attached_drawablep); } + + if (LLSpatialBridge* bridgep = attached_drawablep->getSpatialBridge()) + { + gPipeline.updateMoveNormalAsync(bridgep); + } } } } From 3233b16b9c0b5d2bb7f644b8e68133f0ae6048b4 Mon Sep 17 00:00:00 2001 From: Trish Date: Wed, 9 Sep 2026 13:56:48 -0400 Subject: [PATCH 5/7] adjust attachment policy --- indra/newview/llagentcamera.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index a4704f7640e..36a47545be1 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -150,6 +150,9 @@ static void updateFocusedLinksetObject(LLViewerObject* objectp) } // Attachments have mixed movement sources. Preserve the same policy used by the normal avatar update path. + const bool attachment_selected = + LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 0 && + LLSelectMgr::getInstance()->getSelection()->isAttachment(); for (LLVOAvatar::attachment_map_t::iterator iter = avatarp->mAttachmentPoints.begin(); iter != avatarp->mAttachmentPoints.end(); ) { @@ -179,7 +182,7 @@ static void updateFocusedLinksetObject(LLViewerObject* objectp) if (!attached_drawablep->isState(LLDrawable::EARLY_MOVE)) { - if (attached_object->isSelected()) + if (attachment_selected) { gPipeline.updateMoveNormalAsync(attached_drawablep); } From 918bad4170d7c1e0784a7130047b513999d121a6 Mon Sep 17 00:00:00 2001 From: trish-sl Date: Wed, 9 Sep 2026 14:33:55 -0400 Subject: [PATCH 6/7] Check attachment validity in llagentcamera.cpp Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- indra/newview/llagentcamera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 36a47545be1..6ef8dffcdb2 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -158,7 +158,7 @@ static void updateFocusedLinksetObject(LLViewerObject* objectp) { LLVOAvatar::attachment_map_t::iterator curiter = iter++; LLViewerJointAttachment* attachment = curiter->second; - if (!attachment) + if (!attachment || !attachment->getValid()) { continue; } From 7c1432781b8609546a2294ef0dd12213446fe409 Mon Sep 17 00:00:00 2001 From: Trish Date: Wed, 9 Sep 2026 14:36:19 -0400 Subject: [PATCH 7/7] Update text with the attachment update pass --- indra/newview/llagentcamera.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 6ef8dffcdb2..ea9c49670c0 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -196,6 +196,8 @@ static void updateFocusedLinksetObject(LLViewerObject* objectp) gPipeline.updateMoveNormalAsync(bridgep); } } + + attached_object->updateText(); } } }