Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
37d16bd
Add local mesh preview: load DAE/glTF from disk and render in-world
RyeMutt Jun 3, 2026
3b71784
Local mesh preview: make the in-world object selectable and movable (…
RyeMutt Jun 3, 2026
c6de6a5
Local mesh preview: generate tangents so picking doesn't crash (M2 fix)
RyeMutt Jun 3, 2026
fbff0f3
Local mesh preview: populate the build floater for selected previews …
RyeMutt Jun 3, 2026
f0cf31a
Local mesh preview: normalize merged geometry so bounds/pivot are cor…
RyeMutt Jun 3, 2026
089bdfb
Local mesh preview: live reload on source-file change (M3)
RyeMutt Jun 3, 2026
1bccb2a
Local mesh preview: stop rigged meshes from deforming the agent avatar
RyeMutt Jun 3, 2026
a358d1e
Local mesh preview: clean up preview objects/avatar on shutdown
RyeMutt Jun 3, 2026
d738198
Local mesh preview: spawn the whole model as a linkset, matching the …
RyeMutt Jun 3, 2026
f74e46d
Fix coroutine mutex assert in object weight floater
RyeMutt Jun 3, 2026
35dc9ef
Local mesh preview: make Delete remove the preview linkset
RyeMutt Jun 3, 2026
f15ca38
Local mesh preview: flag client-only objects on LLViewerObject
RyeMutt Jun 3, 2026
8843c1e
Local mesh preview: gate LLViewerObject sim traffic for client-only o…
RyeMutt Jun 3, 2026
3942d33
Local mesh preview: gate LLVOVolume media sim traffic for client-only…
RyeMutt Jun 3, 2026
9fa8c09
Local mesh preview: release previews when their host region is torn down
RyeMutt Jun 3, 2026
d698667
Local mesh preview: keep selection valid through the texture/material…
RyeMutt Jun 3, 2026
4f8a4c7
Local mesh preview: forbid mixing local previews and real objects in …
RyeMutt Jun 3, 2026
f2fa721
Fix excessive warning in console draw
RyeMutt Jun 3, 2026
44e844e
Local mesh preview (M4): keep rigged geometry/skin asset-faithful for…
RyeMutt Jun 3, 2026
dd23696
Local mesh preview (M4): wear a rigged preview on the avatar via righ…
RyeMutt Jun 3, 2026
3d0c482
Local mesh preview (M4): fix attach crash via isLocalOnly guards
RyeMutt Jun 3, 2026
6c96fcf
Local mesh preview (M4): fill rigged face weights from the skin map
RyeMutt Jun 3, 2026
34e12bd
Local mesh preview (M4): don't bake joint-position overrides into rig…
RyeMutt Jun 3, 2026
48d03b3
Local mesh preview (M4): make "Detach" work on rigged previews
RyeMutt Jun 3, 2026
8a3ff61
Local mesh preview (M4): harden detach-on-despawn; doc cleanup
RyeMutt Jun 3, 2026
d17c3d9
Local mesh preview (M4): attach via the normal Attach menu w/ point c…
RyeMutt Jun 3, 2026
cdce3fa
Fix spurious LLSDXMLParser error on single-line <llsd> documents
RyeMutt Jun 3, 2026
530a4a0
Local mesh preview (M5): load + play a local animation on an animesh …
RyeMutt Jun 3, 2026
b9d4877
Local Assets (M6): unified floater for local mesh/anim/texture/material
RyeMutt Jun 4, 2026
2f1e310
Local Assets (M7): mesh materials, apply-to-face, drag-drop, empty hints
RyeMutt Jun 6, 2026
3bec2b9
Local Assets (M8): no double-add, multi-rez with a Rezzed tab, QA polish
RyeMutt Jun 6, 2026
585956e
Local Assets (M9): Upload buttons run local files through the upload …
RyeMutt Jun 6, 2026
a207916
Local Assets (M10): show model-loaded textures/materials, tagged read…
RyeMutt Jun 6, 2026
ecbc509
Local Assets (M11): tooltip coverage across the floater
RyeMutt Jun 6, 2026
db10b2e
Local Assets (M12): release stale mesh-owned imports on reload
RyeMutt Jun 6, 2026
76e30fa
Local Assets: "Apply to Selected" covers the whole object unless a fa…
RyeMutt Jun 6, 2026
211abab
Local previews: real per-sub-mesh name/desc in build floater + block …
RyeMutt Jun 6, 2026
3ef0a34
Build floater: disable sim-only fields for local mesh previews
RyeMutt Jun 6, 2026
f8a8274
Build floater materials: keep GLTF apply/edit local-only-safe for pre…
RyeMutt Jun 6, 2026
399c7b9
Local mesh preview: preserve user face edits across live reload / hot…
RyeMutt Jun 6, 2026
316bd7f
PNG import: accurate 16->8-bit quantization (scale_16 over strip_16)
RyeMutt Jun 6, 2026
1fcbba7
Local mesh preview: fix skewed normals on static rezzed previews
RyeMutt Jun 7, 2026
4d80b0f
Mesh load: fix O(V^2) skin-weight lookup that froze the uploader and …
RyeMutt Jun 7, 2026
6a681dd
Local mesh preview: render every face's glTF material on first load
RyeMutt Jun 7, 2026
a02a6c8
Local Assets: keep the joint-resolution preview avatar at rest pose
RyeMutt Jun 7, 2026
36f2568
Local Assets: mark refresh() override on the two list panels
RyeMutt Jun 7, 2026
72d0c73
Local Assets: address review findings (validation, gating, dedup safety)
RyeMutt Jun 7, 2026
250ed1d
Local Assets: address PR review findings (round 2)
RyeMutt Jun 8, 2026
eda94e0
Local Assets: add to Build menu and a toolbar command
RyeMutt Jun 8, 2026
ae69815
Local Assets: review round 2 follow-ups
RyeMutt Jun 8, 2026
8ade8db
Local mesh preview: honor Replace vs Add for local-preview attach
RyeMutt Jun 8, 2026
27a21e8
Local mesh preview: match upload normals/tangents for static meshes
RyeMutt Jun 8, 2026
389714f
Local Assets: apply textures/materials to every face on the first click
RyeMutt Jun 8, 2026
c53db77
Local Assets: warn when a previewed mesh has no UVs
RyeMutt Jun 8, 2026
6e7cb0d
Local Assets: make object/linkset cost queries local-object safe
RyeMutt Jun 8, 2026
c280453
Local Assets: address review on cost local-safety
RyeMutt Jun 8, 2026
e400efb
Local Assets: harden local-anim live reload and login-time BVH decode
RyeMutt Jun 8, 2026
0e47a38
Fix mac build failure due to unused function
RyeMutt Jun 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions indra/llcommon/llsdserialize_xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,17 @@ void LLSDXMLParser::Impl::parsePart(const char* buf, llssize len)
&& len > 0 )
{
XML_Status status = XML_Parse(mParser, buf, (int)len, 0);
if (status == XML_STATUS_ERROR)
// A short, complete document (e.g. "<llsd><map /></llsd>") may be
// wholly contained in this first chunk. Reaching </llsd> calls
// XML_StopParser(false), which makes XML_Parse return XML_STATUS_ERROR
// even though the parse succeeded -- mGracefullStop distinguishes that
// graceful stop from a real error, matching parse()/parseLines().
if (status == XML_STATUS_ERROR && !mGracefullStop)
{
LL_INFOS() << "Unexpected XML parsing error at start" << LL_ENDL;
if (mEmitErrors)
{
LL_INFOS() << "Unexpected XML parsing error at start" << LL_ENDL;
}
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions indra/llcommon/tests/llsdserialize_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ typedef U32 uint32_t;
#include "../test/namedtempfile.h"
#include "stringize.h"
#include "StringVec.h"
#include "wrapllerrs.h"
#include <functional>

typedef std::function<void(const LLSD& data, std::ostream& str)> FormatterFunction;
Expand Down Expand Up @@ -243,6 +244,33 @@ namespace tut
xml_test("binary", expected);
}

template<> template<>
void sd_xml_object::test<7>()
{
// A complete, single-line legacy <llsd> document (no embedded '\n')
// is read entirely into the header buffer by LLSDSerialize::deserialize
// and handed to LLSDXMLParser::parsePart(). Reaching </llsd> within that
// first chunk calls XML_StopParser(false), which makes expat report
// XML_STATUS_ERROR even though the parse succeeded. parsePart() used to
// log a spurious "Unexpected XML parsing error" for that graceful stop.
// Empty containers are the common single-line form that triggered it.
auto deserialize_no_spurious_error =
[](const std::string& xml, const LLSD& expected)
{
CaptureLog capture(LLError::LEVEL_INFO);
std::istringstream input(xml);
LLSD parsed;
bool ok = LLSDSerialize::deserialize(parsed, input, xml.size());
ensure(STRINGIZE("deserialize " << xml << " succeeded"), ok);
ensure_equals(STRINGIZE("deserialize " << xml << " value"), parsed, expected);
ensure(STRINGIZE("no spurious parse error for " << xml),
capture.messageWith("Unexpected XML parsing error", false).empty());
};

deserialize_no_spurious_error("<llsd><map /></llsd>\n", LLSD::emptyMap());
deserialize_no_spurious_error("<llsd><array /></llsd>\n", LLSD::emptyArray());
}

class TestLLSDSerializeData
{
public:
Expand Down
8 changes: 7 additions & 1 deletion indra/llimage/llpngwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ void LLPngWrapper::normalizeImage()
// 1. Expand any palettes
// 2. Convert grayscales to RGB
// 3. Create alpha layer from transparency
// 4. Ensure 8-bpp for all images
// 4. Ensure 8-bpp for all images (16-bit accurately scaled, not truncated)
// 5. Set (or guess) gamma

if (mColorType == PNG_COLOR_TYPE_PALETTE)
Expand All @@ -263,7 +263,13 @@ void LLPngWrapper::normalizeImage()
}
else if (mBitDepth == 16)
{
#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED
// Accurate linear 16->8 reduction (round(v/257)) rather than
// png_set_strip_16's biased low-byte truncation (v>>8).
png_set_scale_16(mReadPngPtr);
#else
png_set_strip_16(mReadPngPtr);
#endif
}

const F64 SCREEN_GAMMA = 2.2;
Expand Down
70 changes: 68 additions & 2 deletions indra/llprimitive/llmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,12 @@ LLSD LLModel::writeModel(

LLVector3 pos_range = max_pos - min_pos;

// O(1) per-vertex weight lookup for the skinning block below; without
// it the per-vertex getJointInfluences() scan makes this loop O(V^2)
// and freezes the uploader on dense rigged meshes. Built once per
// model (empty/cheap when unskinned).
JointWeightCache weight_cache(*model[idx]);

for (S32 i = 0; i < model[idx]->getNumVolumeFaces(); ++i)
{ //for each face
const LLVolumeFace& face = model[idx]->getVolumeFace(i);
Expand Down Expand Up @@ -1044,10 +1050,10 @@ LLSD LLModel::writeModel(
{
LLVector3 pos(face.mPositions[j].getF32ptr());

weight_list& weights = model[idx]->getJointInfluences(pos);
const weight_list& weights = weight_cache.influences(pos);

S32 count = 0;
for (weight_list::iterator iter = weights.begin(); iter != weights.end(); ++iter)
for (weight_list::const_iterator iter = weights.begin(); iter != weights.end(); ++iter)
{
// Note joint index cannot exceed 255.
if (iter->mJointIdx < 255 && iter->mJointIdx >= 0)
Expand Down Expand Up @@ -1291,6 +1297,66 @@ LLModel::weight_list& LLModel::getJointInfluences(const LLVector3& pos)
}
}

LLModel::JointWeightCache::JointWeightCache(LLModel& model)
: mModel(model)
{
mCells.reserve(model.mSkinWeights.size());
for (const weight_map::value_type& entry : model.mSkinWeights)
{
mCells[cellKey(entry.first)].push_back(&entry);
}
}

LLModel::JointWeightCache::CellKey LLModel::JointWeightCache::cellKey(const LLVector3& p)
{
return { llfloor(p.mV[VX] / WELD_EPSILON),
llfloor(p.mV[VY] / WELD_EPSILON),
llfloor(p.mV[VZ] / WELD_EPSILON) };
}

const LLModel::weight_list& LLModel::JointWeightCache::influences(const LLVector3& pos) const
{
// Match radius == cell size == the weld epsilon, so a key within epsilon of
// pos is in pos's cell or an immediate neighbour. Scan the 3x3x3 block,
// counting in-epsilon candidates and tracking the closest.
const CellKey base = cellKey(pos);
const weight_list* best = nullptr;
F32 best_dist = WELD_EPSILON;
S32 in_epsilon = 0;
for (S32 dx = -1; dx <= 1; ++dx)
{
for (S32 dy = -1; dy <= 1; ++dy)
{
for (S32 dz = -1; dz <= 1; ++dz)
{
auto it = mCells.find({ base.x + dx, base.y + dy, base.z + dz });
if (it == mCells.end())
{
continue;
}
for (const weight_map::value_type* e : it->second)
{
const F32 d = (e->first - pos).length();
if (d < WELD_EPSILON)
{
++in_epsilon;
if (d < best_dist)
{
best_dist = d;
best = &e->second;
}
}
}
}
}
}
// Defer to the full search unless we found exactly one in-epsilon match.
// getJointInfluences() returns the FIRST weld-epsilon match in map order, so
// on a miss (closest-point fallback) or an ambiguous tie (multiple keys
// within epsilon) we mirror it exactly instead of guessing the closest.
return (best && in_epsilon == 1) ? *best : mModel.getJointInfluences(pos);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void LLModel::setConvexHullDecomposition(
const LLModel::convex_hull_decomposition& decomp, const std::vector<LLModel::PhysicsMesh>& decomp_mesh)
{
Expand Down
37 changes: 37 additions & 0 deletions indra/llprimitive/llmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "v4math.h"
#include "m4math.h"
#include <queue>
#include <unordered_map>

class daeElement;
class domMesh;
Expand Down Expand Up @@ -288,6 +289,42 @@ class alignas(16) LLModel : public LLVolume
//get list of weight influences closest to given position
weight_list& getJointInfluences(const LLVector3& pos);

// O(1) accelerator for getJointInfluences(). That function linearly scans
// mSkinWeights, so calling it once per vertex (writeModel, LOD vertex-buffer
// fill, local-mesh preview) is O(V^2) and stalls the main thread for seconds
// on a dense rigged mesh. Build one of these once before a per-vertex loop,
// then call influences() per vertex for an O(1) lookup -- making the weight
// pass O(V). It snapshots pointers into the model's current mSkinWeights, so
// construct it after the weights are final and do not mutate mSkinWeights
// while it is alive. A position with no key within the weld epsilon falls
// back to getJointInfluences() (preserving its exact-find / closest-point
// path), so results are identical to calling that function directly.
class JointWeightCache
{
public:
explicit JointWeightCache(LLModel& model);
const weight_list& influences(const LLVector3& pos) const;

private:
static constexpr F32 WELD_EPSILON = 1e-5f; // == jointPositionalLookup()'s tolerance
struct CellKey
{
S32 x, y, z;
bool operator==(const CellKey& o) const { return x == o.x && y == o.y && z == o.z; }
};
struct CellHash
{
size_t operator()(const CellKey& k) const
{
return (size_t)(((U32)k.x * 73856093u) ^ ((U32)k.y * 19349663u) ^ ((U32)k.z * 83492791u));
}
};
static CellKey cellKey(const LLVector3& p);

LLModel& mModel;
std::unordered_map<CellKey, std::vector<const weight_map::value_type*>, CellHash> mCells;
};

LLMeshSkinInfo mSkinInfo;

std::string mRequestedLabel; // name requested in UI, if any.
Expand Down
2 changes: 1 addition & 1 deletion indra/llui/llconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ void LLConsole::draw()
// draw remaining lines
F32 y_pos = 0.f;

LLUIImagePtr imagep = LLUI::getUIImage("transparent");
LLUIImagePtr imagep = LLUI::getUIImage("transparent.j2c");

static LLCachedControl<F32> console_bg_opacity(*LLUI::getInstance()->mSettingGroups["config"], "ConsoleBackgroundOpacity", 0.7f);
F32 console_opacity = llclamp(console_bg_opacity(), 0.f, 1.f);
Expand Down
8 changes: 8 additions & 0 deletions indra/newview/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ set(viewer_SOURCE_FILES
llfloaterlandholdings.cpp
llfloaterlinkreplace.cpp
llfloaterloadprefpreset.cpp
llfloaterlocalassets.cpp
llfloatermarketplacelistings.cpp
llfloatermap.cpp
llfloatermediasettings.cpp
Expand Down Expand Up @@ -513,6 +514,9 @@ set(viewer_SOURCE_FILES
lllistview.cpp
lllocalbitmaps.cpp
lllocalgltfmaterials.cpp
lllocalmesh.cpp
lllocalanim.cpp
lllocalassetpaths.cpp
lllocationhistory.cpp
lllocationinputctrl.cpp
lllogchat.cpp
Expand Down Expand Up @@ -1144,6 +1148,7 @@ set(viewer_HEADER_FILES
llfloaterlandholdings.h
llfloaterlinkreplace.h
llfloaterloadprefpreset.h
llfloaterlocalassets.h
llfloatermap.h
llfloatermarketplace.h
llfloatermarketplacelistings.h
Expand Down Expand Up @@ -1276,6 +1281,9 @@ set(viewer_HEADER_FILES
lllistview.h
lllocalbitmaps.h
lllocalgltfmaterials.h
lllocalmesh.h
lllocalanim.h
lllocalassetpaths.h
lllocationhistory.h
lllocationinputctrl.h
lllogchat.h
Expand Down
10 changes: 10 additions & 0 deletions indra/newview/app_settings/commands.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@
is_running_function="Floater.IsOpen"
is_running_parameters="inventory"
/>
<command name="local_assets"
available_in_toybox="true"
icon="Command_LocalAssets_Icon"
label_ref="Command_LocalAssets_Label"
tooltip_ref="Command_LocalAssets_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
execute_parameters="local_assets"
is_running_function="Floater.IsOpen"
is_running_parameters="local_assets"
/>
<command name="map"
available_in_toybox="true"
icon="Command_Map_Icon"
Expand Down
Loading