From 6b251fb7a4308b7e2dbf995238866ff46ad6c780 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 17 Jun 2024 13:56:46 +0300 Subject: viewer#1781 More detailed model upload errors for textures Show filename of a specific texture --- indra/llprimitive/llmodel.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 236cef9c3f..9c49f64452 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -66,7 +66,12 @@ LLModel::~LLModel() { if (mDecompID >= 0) { - LLConvexDecomposition::getInstance()->deleteDecomposition(mDecompID); + // can be null on shutdown + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + if (decomp) + { + decomp->deleteDecomposition(mDecompID); + } } mPhysics.mMesh.clear(); } -- cgit v1.3 From 4c60231c3fa52a0875ff5ddd7cc4e416f839da95 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 28 May 2025 12:02:50 +0300 Subject: #4080 Rigged mesh support #6 For now not touching normalizeVolumeFaces() to not brick dae upload --- indra/llprimitive/llmodel.cpp | 156 ++++++++++++++++++++++++++++++++++++ indra/llprimitive/llmodel.h | 1 + indra/newview/gltf/llgltfloader.cpp | 13 ++- 3 files changed, 166 insertions(+), 4 deletions(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 4e3e49ec9f..c67dac2733 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -334,6 +334,162 @@ void LLModel::normalizeVolumeFaces() } } +void LLModel::normalizeVolumeFacesAndWeights() +{ + if (!mVolumeFaces.empty()) + { + LLVector4a min, max; + + // For all of the volume faces + // in the model, loop over + // them and see what the extents + // of the volume along each axis. + min = mVolumeFaces[0].mExtents[0]; + max = mVolumeFaces[0].mExtents[1]; + + for (U32 i = 1; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + update_min_max(min, max, face.mExtents[0]); + update_min_max(min, max, face.mExtents[1]); + + if (face.mTexCoords) + { + LLVector2& min_tc = face.mTexCoordExtents[0]; + LLVector2& max_tc = face.mTexCoordExtents[1]; + + min_tc = face.mTexCoords[0]; + max_tc = face.mTexCoords[0]; + + for (S32 j = 1; j < face.mNumVertices; ++j) + { + update_min_max(min_tc, max_tc, face.mTexCoords[j]); + } + } + else + { + face.mTexCoordExtents[0].set(0, 0); + face.mTexCoordExtents[1].set(1, 1); + } + } + + // Now that we have the extents of the model + // we can compute the offset needed to center + // the model at the origin. + + // Compute center of the model + // and make it negative to get translation + // needed to center at origin. + LLVector4a trans; + trans.setAdd(min, max); + trans.mul(-0.5f); + + // Compute the total size along all + // axes of the model. + LLVector4a size; + size.setSub(max, min); + + // Prevent division by zero. + F32 x = size[0]; + F32 y = size[1]; + F32 z = size[2]; + F32 w = size[3]; + if (fabs(x) < F_APPROXIMATELY_ZERO) + { + x = 1.0; + } + if (fabs(y) < F_APPROXIMATELY_ZERO) + { + y = 1.0; + } + if (fabs(z) < F_APPROXIMATELY_ZERO) + { + z = 1.0; + } + size.set(x, y, z, w); + + // Compute scale as reciprocal of size + LLVector4a scale; + scale.splat(1.f); + scale.div(size); + + LLVector4a inv_scale(1.f); + inv_scale.div(scale); + + for (U32 i = 0; i < mVolumeFaces.size(); ++i) + { + LLVolumeFace& face = mVolumeFaces[i]; + + // We shrink the extents so + // that they fall within + // the unit cube. + // VFExtents change + face.mExtents[0].add(trans); + face.mExtents[0].mul(scale); + + face.mExtents[1].add(trans); + face.mExtents[1].mul(scale); + + // For all the positions, we scale + // the positions to fit within the unit cube. + LLVector4a* pos = (LLVector4a*)face.mPositions; + LLVector4a* norm = (LLVector4a*)face.mNormals; + LLVector4a* t = (LLVector4a*)face.mTangents; + + for (S32 j = 0; j < face.mNumVertices; ++j) + { + pos[j].add(trans); + pos[j].mul(scale); + if (norm && !norm[j].equals3(LLVector4a::getZero())) + { + norm[j].mul(inv_scale); + norm[j].normalize3(); + } + + if (t) + { + F32 w = t[j].getF32ptr()[3]; + t[j].mul(inv_scale); + t[j].normalize3(); + t[j].getF32ptr()[3] = w; + } + } + } + + weight_map old_weights = mSkinWeights; + mSkinWeights.clear(); + mPosition.clear(); + + for (auto& weights : old_weights) + { + LLVector4a pos(weights.first.mV[VX], weights.first.mV[VY], weights.first.mV[VZ]); + pos.add(trans); + pos.mul(scale); + LLVector3 scaled_pos(pos.getF32ptr()); + mPosition.push_back(scaled_pos); + mSkinWeights[scaled_pos] = weights.second; + } + + // mNormalizedScale is the scale at which + // we would need to multiply the model + // by to get the original size of the + // model instead of the normalized size. + LLVector4a normalized_scale; + normalized_scale.splat(1.f); + normalized_scale.div(scale); + mNormalizedScale.set(normalized_scale.getF32ptr()); + mNormalizedTranslation.set(trans.getF32ptr()); + mNormalizedTranslation *= -1.f; + + // remember normalized scale so original dimensions can be recovered for mesh processing (i.e. tangent generation) + for (auto& face : mVolumeFaces) + { + face.mNormalizedScale = mNormalizedScale; + } + } +} + void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const { scale_out = mNormalizedScale; diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index fe28926720..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -202,6 +202,7 @@ public: void sortVolumeFacesByMaterialName(); void normalizeVolumeFaces(); + void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); void optimizeVolumeFaces(); diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 1f8733f4ff..f16efe2ff1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -468,6 +468,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& if (skinIdx >= 0) { + vert.weights = glm::vec4(prim.mWeights[i]); + auto accessorIdx = prim.mAttributes["JOINTS_0"]; LL::GLTF::Accessor::ComponentType componentType = LL::GLTF::Accessor::ComponentType::UNSIGNED_BYTE; if (accessorIdx >= 0) @@ -487,8 +489,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { vert.joints = glm::unpackUint4x16(prim.mJoints[i]); } - - vert.weights = glm::vec4(prim.mWeights[i]); + else + { + vert.joints = glm::zero(); + vert.weights = glm::zero(); + } } vertices.push_back(vert); } @@ -659,8 +664,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - // Call normalizeVolumeFaces to compute proper extents - pModel->normalizeVolumeFaces(); + // Call normalizeVolumeFacesAndWeights to compute proper extents + pModel->normalizeVolumeFacesAndWeights(); // Fill joint names, bind matrices and prepare to remap weight indices if (skinIdx >= 0) -- cgit v1.3 From 08f6f5c697fce4ccbfba357ab9ce5af915dd0574 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 4 Jun 2025 20:57:10 +0300 Subject: #4214 Weights and Joints remap --- indra/llprimitive/llmodel.cpp | 87 +++++++++++++++++++++++++++++++++++++ indra/llprimitive/llmodel.h | 1 + indra/newview/gltf/llgltfloader.cpp | 6 +-- 3 files changed, 90 insertions(+), 4 deletions(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index c67dac2733..bc9f62b6c0 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -112,6 +112,93 @@ void LLModel::remapVolumeFaces() } } +void LLModel::remapSkinWeightsAndJoints() +{ + if (mSkinWeights.empty()) + { + return; + } + + mPosition.clear(); + + // Make a list of positions + std::set positions; + for (S32 i = 0; i < getNumVolumeFaces(); ++i) + { + for (S32 j = 0; j < mVolumeFaces[i].mNumVertices; ++j) + { + positions.emplace(mVolumeFaces[i].mPositions[j].getF32ptr()); + } + } + + // Build new list of weights and record used joints + weight_map replacement_weights; + std::vector joint_index_use_count; + size_t joint_count = mSkinInfo.mJointNames.size(); + joint_index_use_count.resize(joint_count, 0); + for (const LLVector3& pos : positions) + { + mPosition.push_back(pos); + auto found = mSkinWeights.find(pos); + if (found != mSkinWeights.end()) + { + replacement_weights[pos] = found->second; + + for (auto& weight : found->second) + { + if (joint_count > weight.mJointIdx) + { + joint_index_use_count[weight.mJointIdx]++; + } + } + } + } + + // go over joint data and remap joints + // prepare joint map + std::vector replacement_joint_names; + std::vector replacement_joint_nums; + LLMeshSkinInfo::matrix_list_t replacement_inv_bind; + LLMeshSkinInfo::matrix_list_t replacement_alt_bind; + std::vector index_map; + index_map.resize(joint_count); + S32 replacement_index = 0; + + for (S32 i = 0; i < joint_count; i++) + { + if (joint_index_use_count[i] > 0) + { + replacement_joint_names.push_back(mSkinInfo.mJointNames[i]); + replacement_joint_nums.push_back(mSkinInfo.mJointNums[i]); + replacement_inv_bind.push_back(mSkinInfo.mInvBindMatrix[i]); + replacement_alt_bind.push_back(mSkinInfo.mAlternateBindMatrix[i]); + index_map[i] = replacement_index++; + } + } + + // Apply new data + mSkinInfo.mJointNames.clear(); + mSkinInfo.mJointNames = replacement_joint_names; + mSkinInfo.mJointNums.clear(); + mSkinInfo.mJointNums = replacement_joint_nums; + mSkinInfo.mInvBindMatrix.clear(); + mSkinInfo.mInvBindMatrix = replacement_inv_bind; + mSkinInfo.mAlternateBindMatrix.clear(); + mSkinInfo.mAlternateBindMatrix = replacement_alt_bind; + + // remap weights + for (auto& weights : replacement_weights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = index_map[weight.mJointIdx]; + } + } + + mSkinWeights.clear(); + mSkinWeights = replacement_weights; +} + void LLModel::optimizeVolumeFaces() { for (S32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..7fa3a00ee2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,6 +205,7 @@ public: void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); + void remapSkinWeightsAndJoints(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index e330c1d611..60c6832058 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -165,9 +165,6 @@ void LLGLTFLoader::addModelToScene( { current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder); - // remove unused/redundant vertices after normalizing - current_model->remapVolumeFaces(); - volume_faces = static_cast(remainder.size()); // Don't add to scene yet because weights and materials aren't ready. @@ -215,7 +212,8 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices current_model->remapVolumeFaces(); - // Todo: go over skin weights, joints, matrices and remove unused ones + // remove unused/redundant weights and joints + current_model->remapSkinWeightsAndJoints(); mModelList.push_back(model); -- cgit v1.3 From 98abff90a74536c9fa3a7c6d5078fc4b037c9c67 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 10 Jun 2025 23:14:25 +0300 Subject: #4248 Add safety checks to LLMeshSkinInfo::asLLSD() --- indra/llprimitive/llmodel.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index bc9f62b6c0..6e45e9b1bf 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1804,13 +1804,21 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - for (U32 j = 0; j < 4; j++) + if (i < mInvBindMatrix.size()) { - for (U32 k = 0; k < 4; k++) + for (U32 j = 0; j < 4; j++) { - ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; + for (U32 k = 0; k < 4; k++) + { + ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; + } } } + else + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " + << mInvBindMatrix.size() << LL_ENDL; + } } for (U32 i = 0; i < 4; i++) @@ -1821,17 +1829,25 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } - if ( include_joints && mAlternateBindMatrix.size() > 0 ) + if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { - for (U32 j = 0; j < 4; j++) + if (i < mAlternateBindMatrix.size()) { - for (U32 k = 0; k < 4; k++) + for (U32 j = 0; j < 4; j++) { - ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + for (U32 k = 0; k < 4; k++) + { + ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; + } } } + else + { + LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " + << mAlternateBindMatrix.size() << LL_ENDL; + } } if (lock_scale_if_joint_position) -- cgit v1.3 From 5bc92322e974c6c1a0fcc7db0ebb6479ed486a0b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 18 Jun 2025 21:55:57 +0300 Subject: #4250 Crash uploading a dae model --- indra/llprimitive/llmodel.cpp | 48 ++++++++++++++++++++++------------------ indra/newview/llskinningutil.cpp | 3 ++- 2 files changed, 28 insertions(+), 23 deletions(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 6e45e9b1bf..e47c0d8d07 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1804,21 +1804,23 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi { ret["joint_names"][i] = mJointNames[i]; - if (i < mInvBindMatrix.size()) - { - for (U32 j = 0; j < 4; j++) - { - for (U32 k = 0; k < 4; k++) - { - ret["inverse_bind_matrix"][i][j*4+k] = mInvBindMatrix[i].mMatrix[j][k]; - } - } - } - else + // For model to work at all there must be a matching bind matrix, + // so supply an indentity one if it isn't true + // Note: can build an actual bind matrix from joints + const LLMatrix4a& inv_bind = mInvBindMatrix.size() > i ? mInvBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mInvBindMatrix.size()) { LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds inverse bind matrix size " << mInvBindMatrix.size() << LL_ENDL; } + + for (U32 j = 0; j < 4; j++) + { + for (U32 k = 0; k < 4; k++) + { + ret["inverse_bind_matrix"][i][j * 4 + k] = inv_bind.mMatrix[j][k]; + } + } } for (U32 i = 0; i < 4; i++) @@ -1829,25 +1831,27 @@ LLSD LLMeshSkinInfo::asLLSD(bool include_joints, bool lock_scale_if_joint_positi } } + // optional 'joint overrides' if (include_joints && mAlternateBindMatrix.size() > 0) { for (U32 i = 0; i < mJointNames.size(); ++i) { - if (i < mAlternateBindMatrix.size()) - { - for (U32 j = 0; j < 4; j++) - { - for (U32 k = 0; k < 4; k++) - { - ret["alt_inverse_bind_matrix"][i][j*4+k] = mAlternateBindMatrix[i].mMatrix[j][k]; - } - } - } - else + // If there is not enough to match mJointNames, + // either supply no alternate matrixes at all or supply + // replacements + const LLMatrix4a& alt_bind = mAlternateBindMatrix.size() > i ? mAlternateBindMatrix[i] : LLMatrix4a::identity(); + if (i >= mAlternateBindMatrix.size()) { LL_WARNS("MESHSKININFO") << "Joint index " << i << " (" << mJointNames[i] << ") exceeds alternate bind matrix size " << mAlternateBindMatrix.size() << LL_ENDL; } + for (U32 j = 0; j < 4; j++) + { + for (U32 k = 0; k < 4; k++) + { + ret["alt_inverse_bind_matrix"][i][j * 4 + k] = alt_bind.mMatrix[j][k]; + } + } } if (lock_scale_if_joint_position) diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index 43836a420b..47f58afa00 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -360,7 +360,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a { rig_info_tab[joint_num].setIsRiggedTo(true); - const LLMatrix4a& mat = skin->mBindPoseMatrix[joint_index]; + size_t bind_poses_size = skin->mBindPoseMatrix.size(); + const LLMatrix4a& mat = bind_poses_size > joint_index ? skin->mBindPoseMatrix[joint_index] : LLMatrix4a::identity(); LLVector4a pos_joint_space; mat.affineTransform(pos, pos_joint_space); -- cgit v1.3 From f5320302d4b81825b1037ca0074a65ad9eac14ac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 19 Jun 2025 19:20:35 +0300 Subject: #4214 Revert and remake "weights remap" This reverts commits fe10a83f69bb26eb581e143fb99c1250c355938b and 08f6f5c697fce4ccbfba357ab9ce5af915dd0574.. --- indra/llprimitive/llmodel.cpp | 87 ---------------------- indra/llprimitive/llmodel.h | 1 - indra/newview/gltf/llgltfloader.cpp | 79 ++++++++++++++++++-- indra/newview/gltf/llgltfloader.h | 3 + .../skins/default/xui/en/floater_model_preview.xml | 2 + 5 files changed, 78 insertions(+), 94 deletions(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index e47c0d8d07..3d31cfbb7f 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -112,93 +112,6 @@ void LLModel::remapVolumeFaces() } } -void LLModel::remapSkinWeightsAndJoints() -{ - if (mSkinWeights.empty()) - { - return; - } - - mPosition.clear(); - - // Make a list of positions - std::set positions; - for (S32 i = 0; i < getNumVolumeFaces(); ++i) - { - for (S32 j = 0; j < mVolumeFaces[i].mNumVertices; ++j) - { - positions.emplace(mVolumeFaces[i].mPositions[j].getF32ptr()); - } - } - - // Build new list of weights and record used joints - weight_map replacement_weights; - std::vector joint_index_use_count; - size_t joint_count = mSkinInfo.mJointNames.size(); - joint_index_use_count.resize(joint_count, 0); - for (const LLVector3& pos : positions) - { - mPosition.push_back(pos); - auto found = mSkinWeights.find(pos); - if (found != mSkinWeights.end()) - { - replacement_weights[pos] = found->second; - - for (auto& weight : found->second) - { - if (joint_count > weight.mJointIdx) - { - joint_index_use_count[weight.mJointIdx]++; - } - } - } - } - - // go over joint data and remap joints - // prepare joint map - std::vector replacement_joint_names; - std::vector replacement_joint_nums; - LLMeshSkinInfo::matrix_list_t replacement_inv_bind; - LLMeshSkinInfo::matrix_list_t replacement_alt_bind; - std::vector index_map; - index_map.resize(joint_count); - S32 replacement_index = 0; - - for (S32 i = 0; i < joint_count; i++) - { - if (joint_index_use_count[i] > 0) - { - replacement_joint_names.push_back(mSkinInfo.mJointNames[i]); - replacement_joint_nums.push_back(mSkinInfo.mJointNums[i]); - replacement_inv_bind.push_back(mSkinInfo.mInvBindMatrix[i]); - replacement_alt_bind.push_back(mSkinInfo.mAlternateBindMatrix[i]); - index_map[i] = replacement_index++; - } - } - - // Apply new data - mSkinInfo.mJointNames.clear(); - mSkinInfo.mJointNames = replacement_joint_names; - mSkinInfo.mJointNums.clear(); - mSkinInfo.mJointNums = replacement_joint_nums; - mSkinInfo.mInvBindMatrix.clear(); - mSkinInfo.mInvBindMatrix = replacement_inv_bind; - mSkinInfo.mAlternateBindMatrix.clear(); - mSkinInfo.mAlternateBindMatrix = replacement_alt_bind; - - // remap weights - for (auto& weights : replacement_weights) - { - for (auto& weight : weights.second) - { - weight.mJointIdx = index_map[weight.mJointIdx]; - } - } - - mSkinWeights.clear(); - mSkinWeights = replacement_weights; -} - void LLModel::optimizeVolumeFaces() { for (S32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 7fa3a00ee2..5c6d0a55d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,7 +205,6 @@ public: void normalizeVolumeFacesAndWeights(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); - void remapSkinWeightsAndJoints(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 50ce075d65..e495abfe75 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -225,8 +225,6 @@ void LLGLTFLoader::addModelToScene( { // remove unused/redundant vertices model->remapVolumeFaces(); - // remove unused/redundant weights and joints - model->remapSkinWeightsAndJoints(); mModelList.push_back(model); @@ -821,21 +819,25 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& && vertices[i].weights.x > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.x, vertices[i].weights.x)); + gltf_joint_index_use[vertices[i].joints.x]++; } if (gltf_joint_index_use[vertices[i].joints.y] >= 0 && vertices[i].weights.y > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.y, vertices[i].weights.y)); + gltf_joint_index_use[vertices[i].joints.y]++; } if (gltf_joint_index_use[vertices[i].joints.z] >= 0 && vertices[i].weights.z > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.z, vertices[i].weights.z)); + gltf_joint_index_use[vertices[i].joints.z]++; } if (gltf_joint_index_use[vertices[i].joints.w] >= 0 && vertices[i].weights.w > 0.f) { weight_list.push_back(LLModel::JointWeight(vertices[i].joints.w, vertices[i].weights.w)); + gltf_joint_index_use[vertices[i].joints.w]++; } std::sort(weight_list.begin(), weight_list.end(), LLModel::CompareWeightGreater()); @@ -1022,14 +1024,23 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx]; LLMeshSkinInfo& skin_info = pModel->mSkinInfo; + S32 valid_joints_count = mValidJointsCount[skinIdx]; + std::vector gltfindex_to_joitindex_map; size_t jointCnt = gltf_skin.mJoints.size(); + gltfindex_to_joitindex_map.resize(jointCnt); S32 replacement_index = 0; + S32 stripped_valid_joints = 0; for (size_t i = 0; i < jointCnt; ++i) { // Process joint name and idnex S32 joint = gltf_skin.mJoints[i]; + if (gltf_joint_index_use[i] < 0) + { + // unsupported (-1) joint, drop it + continue; + } LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint]; std::string legal_name(jointNode.mName); @@ -1037,10 +1048,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& { legal_name = mJointMap[legal_name]; } - // else thanks to gltf_joint_index_usage any illegal - // joint should have zero uses. - // Add them anyway to preserve order, remapSkinWeightsAndJoints - // will sort them out later + else + { + llassert(false); // should have been stopped by gltf_joint_index_use[i] == -1 + continue; + } + + if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT + && gltf_joint_index_use[i] == 0 + && legal_name != "mPelvis") + { + // Unused (0) joint + // It's perfectly valid to have more joints than is in use + // Ex: sandals that make your legs digitigrade despite not skining to + // knees or the like. + // But if model is over limid, drop extras sans pelvis. + // Keeping 'pelvis' is a workaround to keep preview whole. + // Todo: consider improving this, either take as much as possible or + // ensure common parents/roots are included + continue; + } + + gltfindex_to_joitindex_map[i] = replacement_index++; skin_info.mJointNames.push_back(legal_name); skin_info.mJointNums.push_back(-1); @@ -1050,6 +1079,28 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[skinIdx][i]); } + + if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel + << " Count: " << (S32)skin_info.mInvBindMatrix.size() + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + LLSD args; + args["Message"] = "ModelTooManyJoint"; + args["MODEL_NAME"] = pModel->mLabel; + args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + + // Remap indices for pModel->mSkinWeights + for (auto& weights : pModel->mSkinWeights) + { + for (auto& weight : weights.second) + { + weight.mJointIdx = gltfindex_to_joitindex_map[weight.mJointIdx]; + } + } } return true; @@ -1075,6 +1126,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { mInverseBindMatrices.resize(skin_idx + 1); mAlternateBindMatrices.resize(skin_idx + 1); + mValidJointsCount.resize(skin_idx + 1, 0); } // fill up joints related data @@ -1095,6 +1147,7 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) { data.mName = mJointMap[jointNode.mName]; data.mIsValidViewerJoint = true; + mValidJointsCount[skin_idx]++; } else { @@ -1111,6 +1164,20 @@ void LLGLTFLoader::populateJointFromSkin(S32 skin_idx) } } + if (mValidJointsCount[skin_idx] > LL_MAX_JOINTS_PER_MESH_OBJECT) + { + LL_WARNS("GLTF_IMPORT") << "Too many jonts, will strip unused joints" + << " Count: " << mValidJointsCount[skin_idx] + << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + + LLSD args; + args["Message"] = "SkinJointsOverLimit"; + args["SKIN_INDEX"] = (S32)skin_idx; + args["JOINT_COUNT"] = mValidJointsCount[skin_idx]; + args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + mWarningsArray.append(args); + } + // Go over viewer joints and build overrides glm::mat4 ident(1.0); for (auto &viewer_data : mViewerJointData) diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index aa77c39030..393df8c4ee 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -207,6 +207,9 @@ protected: bind_matrices_t mInverseBindMatrices; bind_matrices_t mAlternateBindMatrices; + // per skin joint count, needs to be tracked for the sake of limits check. + std::vector mValidJointsCount; + private: bool parseMeshes(); void uploadMeshes(); diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 6c177e5008..4c60320a94 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -72,6 +72,8 @@ Unable to load model, unsupported extension: [EXT] Model contains [PART_COUNT] mesh parts. Maximum allowed: [LIMIT] Failed to create temporary file for embedded texture [TEXTURE_INDEX]: [TEMP_FILE] + Skin [SKIN_INDEX] defines [JOINT_COUNT] compatible joints, maximum is: [MAX]. Unused joints will be stripped on per model basis. + Model [MODEL_NAME] uses [JOINT_COUNT], maximum: [MAX], upload might fail Date: Wed, 2 Jul 2025 23:18:05 +0300 Subject: #4242 Debug dump improvement for better comparison with collada output --- indra/llprimitive/lldaeloader.cpp | 6 ++- indra/llprimitive/lldaeloader.h | 2 +- indra/llprimitive/llmodel.cpp | 12 +++-- indra/llprimitive/llmodel.h | 10 +++- indra/llprimitive/llmodelloader.cpp | 57 ++++++++++++++++++++-- indra/llprimitive/llmodelloader.h | 6 ++- indra/newview/app_settings/settings.xml | 13 ++++- indra/newview/gltf/llgltfloader.cpp | 18 ++++--- indra/newview/gltf/llgltfloader.h | 2 +- indra/newview/llmeshrepository.cpp | 4 +- indra/newview/llmodelpreview.cpp | 6 ++- .../skins/default/xui/en/floater_model_preview.xml | 2 +- 12 files changed, 110 insertions(+), 28 deletions(-) (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index eadb052b38..aeeca9bcfb 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -883,6 +883,7 @@ LLDAELoader::LLDAELoader( std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess) : LLModelLoader( filename, @@ -895,8 +896,9 @@ LLDAELoader::LLDAELoader( jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh), - mGeneratedModelLimit(modelLimit), + maxJointsPerMesh, + modelLimit, + debugMode), mPreprocessDAE(preprocess) { } diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h index dc20feca52..400277d902 100644 --- a/indra/llprimitive/lldaeloader.h +++ b/indra/llprimitive/lldaeloader.h @@ -59,6 +59,7 @@ public: std::map>& jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, bool preprocess); virtual ~LLDAELoader() ; @@ -103,7 +104,6 @@ protected: static std::string preprocessDAE(std::string filename); private: - U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mPreprocessDAE; }; diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 3d31cfbb7f..a33f25307e 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -818,7 +818,7 @@ LLSD LLModel::writeModel( bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite, + EWriteModelMode write_mode, bool as_slm, int submodel_id) { @@ -1097,10 +1097,10 @@ LLSD LLModel::writeModel( } } - return writeModelToStream(ostr, mdl, nowrite, as_slm); + return writeModelToStream(ostr, mdl, write_mode, as_slm); } -LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bool as_slm) +LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, EWriteModelMode write_mode, bool as_slm) { std::string::size_type cur_offset = 0; @@ -1162,7 +1162,11 @@ LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, bool nowrite, bo } } - if (!nowrite) + if (write_mode == WRITE_HUMAN) + { + ostr << mdl; + } + else if (write_mode == WRITE_BINARY) { LLSDSerialize::toBinary(header, ostr); diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 5c6d0a55d2..6501b3dc50 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -160,6 +160,12 @@ public: bool loadSkinInfo(LLSD& header, std::istream& is); bool loadDecomposition(LLSD& header, std::istream& is); + enum EWriteModelMode + { + WRITE_NO = 0, + WRITE_BINARY, + WRITE_HUMAN, + }; static LLSD writeModel( std::ostream& ostr, LLModel* physics, @@ -171,14 +177,14 @@ public: bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - bool nowrite = false, + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false, int submodel_id = 0); static LLSD writeModelToStream( std::ostream& ostr, LLSD& mdl, - bool nowrite = false, bool as_slm = false); + EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false); void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); } diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index a5b5d595a2..61e479c9fd 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -113,7 +113,9 @@ LLModelLoader::LLModelLoader( JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh) + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode) : mJointList( jointTransformMap ) , mJointsFromNode( jointsFromNodes ) , LLThread("Model Loader") @@ -133,6 +135,8 @@ LLModelLoader::LLModelLoader( , mNoOptimize(false) , mCacheOnlyHitIfRigged(false) , mMaxJointsPerMesh(maxJointsPerMesh) +, mGeneratedModelLimit(modelLimit) +, mDebugMode(debugMode) , mJointMap(legalJointNamesMap) { assert_main_thread(); @@ -238,7 +242,9 @@ bool LLModelLoader::doLoadModel() } } - return OpenFile(mFilename); + bool res = OpenFile(mFilename); + dumpDebugData(); // conditional on mDebugMode + return res; } void LLModelLoader::setLoadState(U32 state) @@ -505,6 +511,11 @@ bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector& mdl : mModelList) + { + const LLMeshSkinInfo& skin_info = mdl->mSkinInfo; + size_t joint_count = skin_info.mJointNames.size(); + size_t alt_count = skin_info.mAlternateBindMatrix.size(); + + LLModel::writeModel( + file, + nullptr, + mdl, + nullptr, + nullptr, + nullptr, + mdl->mPhysics, + joint_count > 0, + alt_count > 0, + false, + LLModel::WRITE_HUMAN, + false, + mdl->mSubmodelID); + + file << "\n"; + model_count++; + if (model_count == 5) + { + file << "Too many models, stopping at 5.\n"; + break; + } + } } //called in the main thread diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index caffa34676..06a17f006e 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -129,6 +129,7 @@ public: U32 mMaxJointsPerMesh; + U32 mDebugMode; // see dumDebugData() for details LLModelLoader( std::string filename, @@ -141,7 +142,9 @@ public: JointTransformMap& jointTransformMap, JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, - U32 maxJointsPerMesh); + U32 maxJointsPerMesh, + U32 modelLimit, + U32 debugMode); virtual ~LLModelLoader(); virtual void setNoNormalize() { mNoNormalize = true; } @@ -210,6 +213,7 @@ protected: bool mRigValidJointUpload; U32 mLegacyRigFlags; + U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels bool mNoNormalize; bool mNoOptimize; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 447042ccd5..bfb619ad22 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2,7 +2,7 @@ - ImporterDebug + ImporterDebugVerboseLogging Comment Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines. @@ -35,6 +35,17 @@ Value 768 + ImporterDebugMode + + Comment + At 0 does nothing, at 1 dumps skinning data near orifinal file, at 2 dumps skining data and first 5 models as llsd + Persist + 1 + Type + U32 + Value + 0 + ImporterPreprocessDAE Comment diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index be3b9f0c4d..48343aa169 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -98,6 +98,7 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton) //, //bool preprocess) : LLModelLoader( filename, @@ -110,8 +111,9 @@ LLGLTFLoader::LLGLTFLoader(std::string filename, jointTransformMap, jointsFromNodes, jointAliasMap, - maxJointsPerMesh ) - , mGeneratedModelLimit(modelLimit) + maxJointsPerMesh, + modelLimit, + debugMode) , mViewerJointData(viewer_skeleton) { } @@ -1037,7 +1039,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& size_t jointCnt = gltf_skin.mJoints.size(); gltfindex_to_joitindex_map.resize(jointCnt, -1); - if (valid_joints_count > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (valid_joints_count > (S32)mMaxJointsPerMesh) { std::map goup_use_count; // Assume that 'Torso' group is always in use since that's what everything else is attached to @@ -1092,7 +1094,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& // this step needs only joints that have zero uses continue; } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { break; } @@ -1127,16 +1129,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& } } - if (skin_info.mInvBindMatrix.size() > LL_MAX_JOINTS_PER_MESH_OBJECT) + if (skin_info.mInvBindMatrix.size() > mMaxJointsPerMesh) { + // mMaxJointsPerMesh ususlly is equal to LL_MAX_JOINTS_PER_MESH_OBJECT + // and is 110. LL_WARNS("GLTF_IMPORT") << "Too many jonts in " << pModel->mLabel << " Count: " << (S32)skin_info.mInvBindMatrix.size() - << " Limit:" << (S32)LL_MAX_JOINTS_PER_MESH_OBJECT << LL_ENDL; + << " Limit:" << (S32)mMaxJointsPerMesh << LL_ENDL; LLSD args; args["Message"] = "ModelTooManyJoints"; args["MODEL_NAME"] = pModel->mLabel; args["JOINT_COUNT"] = (S32)skin_info.mInvBindMatrix.size(); - args["MAX"] = (S32)LL_MAX_JOINTS_PER_MESH_OBJECT; + args["MAX"] = (S32)mMaxJointsPerMesh; mWarningsArray.append(args); } diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index 10da5c8651..e250a76982 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -83,6 +83,7 @@ class LLGLTFLoader : public LLModelLoader std::map> & jointAliasMap, U32 maxJointsPerMesh, U32 modelLimit, + U32 debugMode, std::vector viewer_skeleton); //, //bool preprocess ); virtual ~LLGLTFLoader(); @@ -103,7 +104,6 @@ protected: tinygltf::Model mGltfModel; bool mGltfLoaded; bool mApplyXYRotation = false; - U32 mGeneratedModelLimit; // GLTF isn't aware of viewer's skeleton and uses it's own, // so need to take viewer's joints and use them to diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e7e95034b2..9382ebb00b 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2740,7 +2740,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); @@ -2898,7 +2898,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) mUploadSkin, mUploadJoints, mLockScaleIfJointPosition, - false, + LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 6843f7375d..bbded6f189 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -167,7 +167,7 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) , mLastJointUpdate(false) , mFirstSkinUpdate(true) , mHasDegenerate(false) - , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebug", false)) + , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebugVerboseLogging", false)) { mNeedsUpdate = true; mCameraDistance = 0.f; @@ -692,7 +692,7 @@ void LLModelPreview::saveUploadData(const std::string& filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position, - false, true, instance.mModel->mSubmodelID); + LLModel::WRITE_BINARY, true, instance.mModel->mSubmodelID); data["mesh"][instance.mModel->mLocalID] = str.str(); } @@ -807,6 +807,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), gSavedSettings.getBOOL("ImporterPreprocessDAE")); } else @@ -827,6 +828,7 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable joint_alias_map, LLSkinningUtil::getMaxJointCount(), gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getU32("ImporterDebugMode"), viewer_skeleton); } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 4141191038..a86b9c7da2 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1426,7 +1426,7 @@ word_wrap="true"> Date: Thu, 18 Sep 2025 13:38:54 -0400 Subject: Initial VHACD based llconvexdecomposition --- autobuild.xml | 68 +-- indra/CMakeLists.txt | 3 + indra/cmake/CMakeLists.txt | 1 + indra/cmake/LLPhysicsExtensions.cmake | 8 +- indra/cmake/VHACD.cmake | 9 + indra/llconvexdecomposition/CMakeLists.txt | 39 ++ .../llconvexdecomposition.cpp | 83 ++++ .../llconvexdecomposition/llconvexdecomposition.h | 231 ++++++++++ .../llconvexdecompositionvhacd.cpp | 492 +++++++++++++++++++++ .../llconvexdecompositionvhacd.h | 339 ++++++++++++++ indra/llprimitive/CMakeLists.txt | 6 + indra/llprimitive/llmodel.cpp | 4 +- indra/llprimitive/llmodel.h | 3 +- indra/newview/CMakeLists.txt | 8 +- indra/newview/llfloatermodelpreview.cpp | 12 +- indra/newview/llmodelpreview.cpp | 14 +- indra/newview/llviewerobjectlist.cpp | 4 +- .../skins/default/xui/en/floater_model_preview.xml | 149 ++++++- 18 files changed, 1418 insertions(+), 55 deletions(-) create mode 100644 indra/cmake/VHACD.cmake create mode 100644 indra/llconvexdecomposition/CMakeLists.txt create mode 100644 indra/llconvexdecomposition/llconvexdecomposition.cpp create mode 100644 indra/llconvexdecomposition/llconvexdecomposition.h create mode 100644 indra/llconvexdecomposition/llconvexdecompositionvhacd.cpp create mode 100644 indra/llconvexdecomposition/llconvexdecompositionvhacd.h (limited to 'indra/llprimitive/llmodel.cpp') diff --git a/autobuild.xml b/autobuild.xml index c9355c73c0..8a022cd304 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1496,41 +1496,19 @@ platforms - darwin64 - - archive - - hash - f290b000b31f9e36f2489946cbc99f5e - url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/59995/563653/llphysicsextensions_stub-1.0.542456-darwin64-542456.tar.bz2 - - name - darwin64 - - linux64 - - archive - - hash - 711f4ec769e4b5f59ba25ee43c11bcbc - url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/4724/14846/llphysicsextensions_stub-1.0.504712-linux64-504712.tar.bz2 - - name - linux64 - - windows64 + common archive hash - 2e5f1f7046a49d8b0bc295aa878116bc + bc41438b10ac6474cf5560465a3662a64f9e65a81342e4c33f18f6694581c7ee28c9ee6f091c36e80a0b1e10c68205be71eb5f8e40fef115d2c744fc2bbfcb43 + hash_algorithm + blake2b url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60043/564063/llphysicsextensions_stub-1.0.542456-windows-542456.tar.bz2 + https://github.com/AlchemyViewer/llphysicsextensions_stub/releases/download/v1.0-cb4900e/llphysicsextensions_stub-1.0-common-17836965684.tar.zst name - windows64 + common license @@ -1540,7 +1518,7 @@ copyright Copyright (c) 2010, Linden Research, Inc. version - 1.0.542456 + 1.0 name llphysicsextensions_stub @@ -2966,6 +2944,38 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors description Discord Social SDK + vhacd + + platforms + + common + + archive + + hash + 140d8fc952a10edb5f2d72ab405336019ef32cadfa64f0cfce76c9de4bc6268cbc87cc8cd89d3417fb78b531d441701afc8d016bafe4bd275df2707f7daf1387 + hash_algorithm + blake2b + url + https://github.com/AlchemyViewer/3p-vhacd/releases/download/v4.1.0-r2/vhacd-4.1.0-r2-common-18166921729.tar.zst + + name + common + + + license + BSD + license_file + LICENSES/vhacd.txt + copyright + Copyright (c) 2011, Khaled Mamou + version + 4.1.0-r2 + name + vhacd + description + Voxelized Hierarchical Approximate Convex Decomposition + package_description diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 6504002dd9..246bd7bfa0 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -61,6 +61,9 @@ add_subdirectory(cmake) add_subdirectory(${LIBS_OPEN_PREFIX}llaudio) add_subdirectory(${LIBS_OPEN_PREFIX}llappearance) add_subdirectory(${LIBS_OPEN_PREFIX}llcharacter) +if (NOT HAVOK AND NOT HAVOK_TPV) + add_subdirectory(${LIBS_OPEN_PREFIX}llconvexdecomposition) +endif () add_subdirectory(${LIBS_OPEN_PREFIX}llcommon) add_subdirectory(${LIBS_OPEN_PREFIX}llcorehttp) add_subdirectory(${LIBS_OPEN_PREFIX}llimage) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 4608f68f50..2ba282bdb7 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES UI.cmake UnixInstall.cmake Variables.cmake + VHACD.cmake ViewerMiscLibs.cmake VisualLeakDetector.cmake LibVLCPlugin.cmake diff --git a/indra/cmake/LLPhysicsExtensions.cmake b/indra/cmake/LLPhysicsExtensions.cmake index 80d243d9f8..bb6cb703d2 100644 --- a/indra/cmake/LLPhysicsExtensions.cmake +++ b/indra/cmake/LLPhysicsExtensions.cmake @@ -23,9 +23,15 @@ if (HAVOK) use_prebuilt_binary(llphysicsextensions_source) set(LLPHYSICSEXTENSIONS_SRC_DIR ${LIBS_PREBUILT_DIR}/llphysicsextensions/src) target_link_libraries( llphysicsextensions_impl INTERFACE llphysicsextensions) + target_compile_definitions( llphysicsextensions_impl INTERFACE LL_HAVOK=1 ) elseif (HAVOK_TPV) use_prebuilt_binary(llphysicsextensions_tpv) - target_link_libraries( llphysicsextensions_impl INTERFACE llphysicsextensions_tpv) + if(WINDOWS) + target_link_libraries( llphysicsextensions_impl INTERFACE ${ARCH_PREBUILT_DIRS}/llphysicsextensions_tpv.lib) + else() + target_link_libraries( llphysicsextensions_impl INTERFACE ${ARCH_PREBUILT_DIRS}/libllphysicsextensions_tpv.a) + endif() + target_compile_definitions( llphysicsextensions_impl INTERFACE LL_HAVOK=1 ) else (HAVOK) use_prebuilt_binary(llphysicsextensions_stub) set(LLPHYSICSEXTENSIONS_SRC_DIR ${LIBS_PREBUILT_DIR}/llphysicsextensions/stub) diff --git a/indra/cmake/VHACD.cmake b/indra/cmake/VHACD.cmake new file mode 100644 index 0000000000..9f37f32b2d --- /dev/null +++ b/indra/cmake/VHACD.cmake @@ -0,0 +1,9 @@ +# -*- cmake -*- +include(Prebuilt) + +add_library(ll::vhacd INTERFACE IMPORTED) + +use_system_binary(vhacd) +use_prebuilt_binary(vhacd) + +target_include_directories(ll::vhacd SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/vhacd/) diff --git a/indra/llconvexdecomposition/CMakeLists.txt b/indra/llconvexdecomposition/CMakeLists.txt new file mode 100644 index 0000000000..7dae884db8 --- /dev/null +++ b/indra/llconvexdecomposition/CMakeLists.txt @@ -0,0 +1,39 @@ +# -*- cmake -*- + +project(llconvexdecomposition) + +include(00-Common) +include(LLCommon) +include(LLMath) +include(VHACD) + +set(llconvexdecomposition_SOURCE_FILES + llconvexdecomposition.cpp + llconvexdecompositionvhacd.cpp + ) + +set(llconvexdecomposition_HEADER_FILES + CMakeLists.txt + llconvexdecomposition.h + llconvexdecompositionvhacd.h + ) + +set_source_files_properties(${llconvexdecomposition_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND llconvexdecomposition_SOURCE_FILES ${llconvexdecomposition_HEADER_FILES}) + +add_library (llconvexdecomposition ${llconvexdecomposition_SOURCE_FILES}) +target_include_directories(llconvexdecomposition INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(llconvexdecomposition + llcommon + llmath + ll::vhacd) + +if(WINDOWS) + target_compile_options(llconvexdecomposition PRIVATE /bigobj) +endif() + +# Add tests + diff --git a/indra/llconvexdecomposition/llconvexdecomposition.cpp b/indra/llconvexdecomposition/llconvexdecomposition.cpp new file mode 100644 index 0000000000..7b9d775c53 --- /dev/null +++ b/indra/llconvexdecomposition/llconvexdecomposition.cpp @@ -0,0 +1,83 @@ +/** +* @file llconvexdecomposition.cpp +* @author falcon@lindenlab.com +* @brief Inner implementation of LLConvexDecomposition interface +* +* $LicenseInfo:firstyear=2011&license=lgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2011, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llconvexdecompositionvhacd.h" +#include "llconvexdecomposition.h" + +bool LLConvexDecomposition::s_isInitialized = false; + +// static +bool LLConvexDecomposition::isFunctional() +{ + return LLConvexDecompositionVHACD::isFunctional(); +} + +// static +LLConvexDecomposition* LLConvexDecomposition::getInstance() +{ + if ( !s_isInitialized ) + { + return nullptr; + } + else + { + return LLConvexDecompositionVHACD::getInstance(); + } +} + +// static +LLCDResult LLConvexDecomposition::initSystem() +{ + LLCDResult result = LLConvexDecompositionVHACD::initSystem(); + if ( result == LLCD_OK ) + { + s_isInitialized = true; + } + return result; +} + +// static +LLCDResult LLConvexDecomposition::initThread() +{ + return LLConvexDecompositionVHACD::initThread(); +} + +// static +LLCDResult LLConvexDecomposition::quitThread() +{ + return LLConvexDecompositionVHACD::quitThread(); +} + +// static +LLCDResult LLConvexDecomposition::quitSystem() +{ + return LLConvexDecompositionVHACD::quitSystem(); +} + + diff --git a/indra/llconvexdecomposition/llconvexdecomposition.h b/indra/llconvexdecomposition/llconvexdecomposition.h new file mode 100644 index 0000000000..8008bc6e12 --- /dev/null +++ b/indra/llconvexdecomposition/llconvexdecomposition.h @@ -0,0 +1,231 @@ +/** + * @file llconvexdecomposition.cpp + * @brief LLConvexDecomposition interface definition + * + * $LicenseInfo:firstyear=2011&license=lgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_CONVEX_DECOMPOSITION +#define LL_CONVEX_DECOMPOSITION + +typedef int bool32; + +#if defined(_WIN32) || defined(_WIN64) +#define LLCD_CALL __cdecl +#else +#define LLCD_CALL +#endif + +struct LLCDParam +{ + enum LLCDParamType + { + LLCD_INVALID = 0, + LLCD_INTEGER, + LLCD_FLOAT, + LLCD_BOOLEAN, + LLCD_ENUM + }; + + struct LLCDEnumItem + { + const char* mName; + int mValue; + }; + + union LLCDValue + { + float mFloat; + int mIntOrEnumValue; + bool32 mBool; + }; + + union LLCDParamDetails + { + struct { + LLCDValue mLow; + LLCDValue mHigh; + LLCDValue mDelta; + } mRange; + + struct { + int mNumEnums; + LLCDEnumItem* mEnumsArray; + } mEnumValues; + }; + + const char* mName; + const char* mDescription; + LLCDParamType mType; + LLCDParamDetails mDetails; + LLCDValue mDefault; + int mStage; + + // WARNING: Only the LLConvexDecomposition implementation + // should change this value + int mReserved; +}; + +struct LLCDStageData +{ + const char* mName; + const char* mDescription; + bool32 mSupportsCallback; +}; + +struct LLCDMeshData +{ + enum IndexType + { + INT_16, + INT_32 + }; + + const float* mVertexBase; + int mVertexStrideBytes; + int mNumVertices; + const void* mIndexBase; + IndexType mIndexType; + int mIndexStrideBytes; + int mNumTriangles; +}; + +struct LLCDHull +{ + const float* mVertexBase; + int mVertexStrideBytes; + int mNumVertices; +}; + +enum LLCDResult +{ + LLCD_OK = 0, + LLCD_UNKOWN_ERROR, + LLCD_NULL_PTR, + LLCD_INVALID_STAGE, + LLCD_UNKNOWN_PARAM, + LLCD_BAD_VALUE, + LLCD_REQUEST_OUT_OF_RANGE, + LLCD_INVALID_MESH_DATA, + LLCD_INVALID_HULL_DATA, + LLCD_STAGE_NOT_READY, + LLCD_INVALID_THREAD, + LLCD_NOT_IMPLEMENTED +}; + +// This callback will receive a string describing the current subtask being performed +// as well as a pair of numbers indicating progress. (The values should not be interpreted +// as a completion percentage as 'current' may be greater than 'final'.) +// If the callback returns zero, the decomposition will be terminated +typedef int (LLCD_CALL *llcdCallbackFunc)(const char* description, int current_progress, int final_progress); + +class LLConvexDecomposition +{ +public: + // Obtain a pointer to the actual implementation + static LLConvexDecomposition* getInstance(); + + /// @returns false if this is the stub + static bool isFunctional(); + + static LLCDResult initSystem(); + static LLCDResult initThread(); + static LLCDResult quitThread(); + static LLCDResult quitSystem(); + + // Generate a decomposition object handle + virtual void genDecomposition(int& decomp) = 0; + // Delete decomposition object handle + virtual void deleteDecomposition(int decomp) = 0; + // Bind given decomposition handle + // Commands operate on currently bound decomposition + virtual void bindDecomposition(int decomp) = 0; + + // Sets *paramsOut to the address of the LLCDParam array and returns + // the number of parameters + virtual int getParameters(const LLCDParam** paramsOut) = 0; + + + // Sets *stagesOut to the address of the LLCDStageData array and returns + // the number of stages + virtual int getStages(const LLCDStageData** stagesOut) = 0; + + + // Set a parameter by name. Pass enum values as integers. + virtual LLCDResult setParam(const char* name, float val) = 0; + virtual LLCDResult setParam(const char* name, int val) = 0; + virtual LLCDResult setParam(const char* name, bool val) = 0; + + + // Set incoming mesh data. Data is copied to local buffers and will + // persist until the next setMeshData call + virtual LLCDResult setMeshData( const LLCDMeshData* data, bool vertex_based ) = 0; + + + // Register a callback to be called periodically during the specified stage + // See the typedef above for more information + virtual LLCDResult registerCallback( int stage, llcdCallbackFunc callback ) = 0; + + + // Execute the specified decomposition stage + virtual LLCDResult executeStage(int stage) = 0; + virtual LLCDResult buildSingleHull() = 0 ; + + + // Gets the number of hulls generated by the specified decompositions stage + virtual int getNumHullsFromStage(int stage) = 0; + + + // Populates hullOut to reference the internal copy of the requested hull + // The data will persist only until the next executeStage call for that stage. + virtual LLCDResult getHullFromStage( int stage, int hull, LLCDHull* hullOut ) = 0; + + virtual LLCDResult getSingleHull( LLCDHull* hullOut ) = 0 ; + + + // TODO: Implement lock of some kind to disallow this call if data not yet ready + // Populates the meshDataOut to reference the utility's copy of the mesh geometry + // for the hull and stage specified. + // You must copy this data if you want to continue using it after the next executeStage + // call + virtual LLCDResult getMeshFromStage( int stage, int hull, LLCDMeshData* meshDataOut) = 0; + + + // Creates a mesh from hullIn and temporarily stores it internally in the utility. + // The mesh data persists only until the next call to getMeshFromHull + virtual LLCDResult getMeshFromHull( LLCDHull* hullIn, LLCDMeshData* meshOut ) = 0; + + // Takes meshIn, generates a single convex hull from it, converts that to a mesh + // stored internally, and populates meshOut to reference the internally stored data. + // The data is persistent only until the next call to generateSingleHullMeshFromMesh + virtual LLCDResult generateSingleHullMeshFromMesh( LLCDMeshData* meshIn, LLCDMeshData* meshOut) = 0; + + // + /// Debug + virtual void loadMeshData(const char* fileIn, LLCDMeshData** meshDataOut) = 0; + +private: + static bool s_isInitialized; +}; + +#endif //LL_CONVEX_DECOMPOSITION + diff --git a/indra/llconvexdecomposition/llconvexdecompositionvhacd.cpp b/indra/llconvexdecomposition/llconvexdecompositionvhacd.cpp new file mode 100644 index 0000000000..78876f9f36 --- /dev/null +++ b/indra/llconvexdecomposition/llconvexdecompositionvhacd.cpp @@ -0,0 +1,492 @@ +/** +* @file llconvexdecompositionvhacd.cpp +* @author rye@alchemyviewer.org +* @brief A VHACD based implementation of LLConvexDecomposition +* +* $LicenseInfo:firstyear=2025&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2025, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llmath.h" +#include "v3math.h" + +#include +#include + +#define ENABLE_VHACD_IMPLEMENTATION 1 +#include "VHACD.h" + +#include "llconvexdecompositionvhacd.h" + +constexpr S32 MAX_HULLS = 256; +constexpr S32 MAX_VERTICES_PER_HULL = 256; + +bool LLConvexDecompositionVHACD::isFunctional() +{ + return true; +} + +LLConvexDecomposition* LLConvexDecompositionVHACD::getInstance() +{ + return LLSimpleton::getInstance(); +} + +LLCDResult LLConvexDecompositionVHACD::initSystem() +{ + createInstance(); + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::initThread() +{ + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::quitThread() +{ + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::quitSystem() +{ + deleteSingleton(); + return LLCD_OK; +} + +LLConvexDecompositionVHACD::LLConvexDecompositionVHACD() +{ + //Create our vhacd instance and setup default parameters + mVHACD = VHACD::CreateVHACD(); + + mVHACDParameters.m_callback = &mVHACDCallback; + mVHACDParameters.m_logger = &mVHACDLogger; + + mDecompStages[0].mName = "Analyze"; + mDecompStages[0].mDescription = nullptr; + + LLCDParam param; + param.mName = "Fill Mode"; + param.mDescription = nullptr; + param.mType = LLCDParam::LLCD_ENUM; + param.mDetails.mEnumValues.mNumEnums = 3; + + static LLCDParam::LLCDEnumItem fill_enums[3]; + fill_enums[(size_t)VHACD::FillMode::FLOOD_FILL].mName = "Flood"; + fill_enums[(size_t)VHACD::FillMode::FLOOD_FILL].mValue = (int)VHACD::FillMode::FLOOD_FILL; + fill_enums[(size_t)VHACD::FillMode::SURFACE_ONLY].mName = "Surface Only"; + fill_enums[(size_t)VHACD::FillMode::SURFACE_ONLY].mValue = (int)VHACD::FillMode::SURFACE_ONLY; + fill_enums[(size_t)VHACD::FillMode::RAYCAST_FILL].mName = "Raycast"; + fill_enums[(size_t)VHACD::FillMode::RAYCAST_FILL].mValue = (int)VHACD::FillMode::RAYCAST_FILL; + + param.mDetails.mEnumValues.mEnumsArray = fill_enums; + param.mDefault.mIntOrEnumValue = (int)VHACD::FillMode::FLOOD_FILL; + param.mStage = 0; + param.mReserved = -1; + mDecompParams.push_back(param); + + enum EVoxelQualityLevels + { + E_LOW_QUALITY = 0, + E_NORMAL_QUALITY, + E_HIGH_QUALITY, + E_VERY_HIGH_QUALITY, + E_ULTRA_QUALITY, + E_MAX_QUALITY, + E_NUM_QUALITY_LEVELS + }; + + param.mName = "Voxel Resolution"; + param.mDescription = nullptr; + param.mType = LLCDParam::LLCD_ENUM; + param.mDetails.mEnumValues.mNumEnums = E_NUM_QUALITY_LEVELS; + + static LLCDParam::LLCDEnumItem voxel_quality_enums[E_NUM_QUALITY_LEVELS]; + voxel_quality_enums[E_LOW_QUALITY].mName = "Low"; + voxel_quality_enums[E_LOW_QUALITY].mValue = 200000; + voxel_quality_enums[E_NORMAL_QUALITY].mName = "Normal"; + voxel_quality_enums[E_NORMAL_QUALITY].mValue = 400000; + voxel_quality_enums[E_HIGH_QUALITY].mName = "High"; + voxel_quality_enums[E_HIGH_QUALITY].mValue = 800000; + voxel_quality_enums[E_VERY_HIGH_QUALITY].mName = "Very High"; + voxel_quality_enums[E_VERY_HIGH_QUALITY].mValue = 1200000; + voxel_quality_enums[E_ULTRA_QUALITY].mName = "Ultra"; + voxel_quality_enums[E_ULTRA_QUALITY].mValue = 1600000; + voxel_quality_enums[E_MAX_QUALITY].mName = "Maximum"; + voxel_quality_enums[E_MAX_QUALITY].mValue = 2000000; + + param.mDetails.mEnumValues.mEnumsArray = voxel_quality_enums; + param.mDefault.mIntOrEnumValue = 400000; + param.mStage = 0; + param.mReserved = -1; + mDecompParams.push_back(param); + + param.mName = "Num Hulls"; + param.mDescription = nullptr; + param.mType = LLCDParam::LLCD_FLOAT; + param.mDetails.mRange.mLow.mFloat = 1.f; + param.mDetails.mRange.mHigh.mFloat = MAX_HULLS; + param.mDetails.mRange.mDelta.mFloat = 1.f; + param.mDefault.mFloat = 8.f; + param.mStage = 0; + param.mReserved = -1; + mDecompParams.push_back(param); + + param.mName = "Num Vertices"; + param.mDescription = nullptr; + param.mType = LLCDParam::LLCD_FLOAT; + param.mDetails.mRange.mLow.mFloat = 3.f; + param.mDetails.mRange.mHigh.mFloat = MAX_VERTICES_PER_HULL; + param.mDetails.mRange.mDelta.mFloat = 1.f; + param.mDefault.mFloat = 32.f; + param.mStage = 0; + param.mReserved = -1; + mDecompParams.push_back(param); + + param.mName = "Error Tolerance"; + param.mDescription = nullptr; + param.mType = LLCDParam::LLCD_FLOAT; + param.mDetails.mRange.mLow.mFloat = 0.0001f; + param.mDetails.mRange.mHigh.mFloat = 99.f; + param.mDetails.mRange.mDelta.mFloat = 0.001f; + param.mDefault.mFloat = 1.f; + param.mStage = 0; + param.mReserved = -1; + mDecompParams.push_back(param); + + for (const LLCDParam& param : mDecompParams) + { + const char* const name = param.mName; + + switch (param.mType) + { + case LLCDParam::LLCD_FLOAT: + { + setParam(name, param.mDefault.mFloat); + break; + } + case LLCDParam::LLCD_ENUM: + case LLCDParam::LLCD_INTEGER: + { + setParam(name, param.mDefault.mIntOrEnumValue); + break; + } + case LLCDParam::LLCD_BOOLEAN: + { + setParam(name, (param.mDefault.mBool != 0)); + break; + } + case LLCDParam::LLCD_INVALID: + default: + { + break; + } + } + } +} + +LLConvexDecompositionVHACD::~LLConvexDecompositionVHACD() +{ + mBoundDecomp = nullptr; + mDecompData.clear(); + + mVHACD->Release(); +} + +void LLConvexDecompositionVHACD::genDecomposition(int& decomp) +{ + int new_decomp_id = static_cast(mDecompData.size()) + 1; + mDecompData[new_decomp_id] = LLDecompData(); + decomp = new_decomp_id; +} + +void LLConvexDecompositionVHACD::deleteDecomposition(int decomp) +{ + auto iter = mDecompData.find(decomp); + if (iter != mDecompData.end()) + { + if (mBoundDecomp == &iter->second) + { + mBoundDecomp = nullptr; + } + mDecompData.erase(iter); + } +} + +void LLConvexDecompositionVHACD::bindDecomposition(int decomp) +{ + auto iter = mDecompData.find(decomp); + if (iter != mDecompData.end()) + { + mBoundDecomp = &iter->second; + } + else + { + LL_WARNS() << "Failed to bind unknown decomposition: " << decomp << LL_ENDL; + mBoundDecomp = nullptr; + } +} + +LLCDResult LLConvexDecompositionVHACD::setParam(const char* name, float val) +{ + if (name == std::string("Num Hulls")) + { + mVHACDParameters.m_maxConvexHulls = llclamp(ll_round(val), 1, MAX_HULLS); + } + else if (name == std::string("Num Vertices")) + { + mVHACDParameters.m_maxNumVerticesPerCH = llclamp(ll_round(val), 3, MAX_VERTICES_PER_HULL); + } + else if (name == std::string("Error Tolerance")) + { + mVHACDParameters.m_minimumVolumePercentErrorAllowed = val; + } + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::setParam(const char* name, bool val) +{ + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::setParam(const char* name, int val) +{ + if (name == std::string("Fill Mode")) + { + mVHACDParameters.m_fillMode = (VHACD::FillMode)val; + } + else if (name == std::string("Voxel Resolution")) + { + mVHACDParameters.m_resolution = val; + } + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::setMeshData( const LLCDMeshData* data, bool vertex_based ) +{ + if (!mBoundDecomp) + { + return LLCD_NULL_PTR; + } + + return mBoundDecomp->mSourceMesh.from(data, vertex_based); +} + +LLCDResult LLConvexDecompositionVHACD::registerCallback(int stage, llcdCallbackFunc callback ) +{ + if (stage == 0) + { + mVHACDCallback.setCallbackFunc(callback); + return LLCD_OK; + } + else + { + return LLCD_INVALID_STAGE; + } +} + +LLCDResult LLConvexDecompositionVHACD::executeStage(int stage) +{ + if (!mBoundDecomp) + { + return LLCD_NULL_PTR; + } + + if (stage != 0) + { + return LLCD_INVALID_STAGE; + } + + mBoundDecomp->mDecomposedHulls.clear(); + + const auto& decomp_mesh = mBoundDecomp->mSourceMesh; + if (!mVHACD->Compute((const double* const)decomp_mesh.mVertices.data(), static_cast(decomp_mesh.mVertices.size()), (const uint32_t* const)decomp_mesh.mIndices.data(), static_cast(decomp_mesh.mIndices.size()), mVHACDParameters)) + { + return LLCD_INVALID_HULL_DATA; + } + + uint32_t num_nulls = mVHACD->GetNConvexHulls(); + if (num_nulls == 0) + { + return LLCD_INVALID_HULL_DATA; + } + + for (uint32_t i = 0; num_nulls > i; ++i) + { + VHACD::IVHACD::ConvexHull ch; + if (!mVHACD->GetConvexHull(i, ch)) + continue; + + LLConvexMesh out_mesh; + out_mesh.setVertices(ch.m_points); + out_mesh.setIndices(ch.m_triangles); + + mBoundDecomp->mDecomposedHulls.push_back(std::move(out_mesh)); + } + + mVHACD->Clean(); + + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::buildSingleHull() +{ + LL_INFOS() << "Building single hull mesh" << LL_ENDL; + if (!mBoundDecomp || mBoundDecomp->mSourceMesh.mVertices.empty()) + { + return LLCD_NULL_PTR; + } + + mBoundDecomp->mSingleHullMesh.clear(); + + VHACD::QuickHull quickhull; + uint32_t num_tris = quickhull.ComputeConvexHull(mBoundDecomp->mSourceMesh.mVertices, MAX_VERTICES_PER_HULL); + if (num_tris > 0) + { + mBoundDecomp->mSingleHullMesh.setVertices(quickhull.GetVertices()); + mBoundDecomp->mSingleHullMesh.setIndices(quickhull.GetIndices()); + + return LLCD_OK; + } + + return LLCD_INVALID_MESH_DATA; +} + +int LLConvexDecompositionVHACD::getNumHullsFromStage(int stage) +{ + if (!mBoundDecomp || stage != 0) + { + return 0; + } + + return narrow(mBoundDecomp->mDecomposedHulls.size()); +} + +LLCDResult LLConvexDecompositionVHACD::getSingleHull( LLCDHull* hullOut ) +{ + memset( hullOut, 0, sizeof(LLCDHull) ); + + if (!mBoundDecomp) + { + return LLCD_NULL_PTR; + } + + if (mBoundDecomp->mSingleHullMesh.vertices.empty()) + { + return LLCD_INVALID_HULL_DATA; + } + + mBoundDecomp->mSingleHullMesh.to(hullOut); + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::getHullFromStage( int stage, int hull, LLCDHull* hullOut ) +{ + memset( hullOut, 0, sizeof(LLCDHull) ); + + if (!mBoundDecomp) + { + return LLCD_NULL_PTR; + } + + if (stage != 0) + { + return LLCD_INVALID_STAGE; + } + + if (mBoundDecomp->mDecomposedHulls.empty() || mBoundDecomp->mDecomposedHulls.size() <= hull) + { + return LLCD_REQUEST_OUT_OF_RANGE; + } + + mBoundDecomp->mDecomposedHulls[hull].to(hullOut); + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::getMeshFromStage( int stage, int hull, LLCDMeshData* meshDataOut ) +{ + memset( meshDataOut, 0, sizeof(LLCDMeshData)); + if (!mBoundDecomp) + { + return LLCD_NULL_PTR; + } + + if (stage != 0) + { + return LLCD_INVALID_STAGE; + } + + if (mBoundDecomp->mDecomposedHulls.empty() || mBoundDecomp->mDecomposedHulls.size() <= hull) + { + return LLCD_REQUEST_OUT_OF_RANGE; + } + + mBoundDecomp->mDecomposedHulls[hull].to(meshDataOut); + return LLCD_OK; +} + +LLCDResult LLConvexDecompositionVHACD::getMeshFromHull( LLCDHull* hullIn, LLCDMeshData* meshOut ) +{ + memset(meshOut, 0, sizeof(LLCDMeshData)); + + LLVHACDMesh inMesh(hullIn); + + VHACD::QuickHull quickhull; + uint32_t num_tris = quickhull.ComputeConvexHull(inMesh.mVertices, MAX_VERTICES_PER_HULL); + if (num_tris > 0) + { + mMeshFromHullData.setVertices(quickhull.GetVertices()); + mMeshFromHullData.setIndices(quickhull.GetIndices()); + + mMeshFromHullData.to(meshOut); + return LLCD_OK; + } + + return LLCD_INVALID_HULL_DATA; +} + +LLCDResult LLConvexDecompositionVHACD::generateSingleHullMeshFromMesh(LLCDMeshData* meshIn, LLCDMeshData* meshOut) +{ + memset( meshOut, 0, sizeof(LLCDMeshData) ); + + LLVHACDMesh inMesh(meshIn, true); + + VHACD::QuickHull quickhull; + uint32_t num_tris = quickhull.ComputeConvexHull(inMesh.mVertices, MAX_VERTICES_PER_HULL); + if (num_tris > 0) + { + mSingleHullMeshFromMeshData.setVertices(quickhull.GetVertices()); + mSingleHullMeshFromMeshData.setIndices(quickhull.GetIndices()); + + mSingleHullMeshFromMeshData.to(meshOut); + return LLCD_OK; + } + + return LLCD_INVALID_MESH_DATA; +} + +void LLConvexDecompositionVHACD::loadMeshData(const char* fileIn, LLCDMeshData** meshDataOut) +{ + static LLCDMeshData meshData; + memset( &meshData, 0, sizeof(LLCDMeshData) ); + *meshDataOut = &meshData; +} diff --git a/indra/llconvexdecomposition/llconvexdecompositionvhacd.h b/indra/llconvexdecomposition/llconvexdecompositionvhacd.h new file mode 100644 index 0000000000..675356629c --- /dev/null +++ b/indra/llconvexdecomposition/llconvexdecompositionvhacd.h @@ -0,0 +1,339 @@ +/** +* @file llconvexdecompositionvhacd.h +* @author rye@alchemyviewer.org +* @brief A VHACD based implementation of LLConvexDecomposition +* +* $LicenseInfo:firstyear=2025&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2025, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_CONVEX_DECOMP_UTIL_VHACD_H +#define LL_CONVEX_DECOMP_UTIL_VHACD_H + +#include "llconvexdecomposition.h" +#include "llsingleton.h" +#include "llmath.h" + +#include + +#include "VHACD.h" + +class LLDecompDataVHACD; + +class LLConvexDecompositionVHACD : public LLSimpleton, public LLConvexDecomposition +{ + class VHACDCallback : public VHACD::IVHACD::IUserCallback + { + public: + void Update(const double overallProgress, const double stageProgress, const char* const stage, const char* operation) override + { + std::string out_msg = llformat("Stage: %s Operation: %s", stage, operation); + if (mCurrentStage != stage && mCurrentOperation != operation) + { + mCurrentStage = stage; + mCurrentOperation = operation; + LL_INFOS("VHACD") << out_msg << LL_ENDL; + } + + if(mCallbackFunc) + { + mCallbackFunc(out_msg.c_str(), ll_round(static_cast(stageProgress)), ll_round(static_cast(overallProgress))); + } + } + + void setCallbackFunc(llcdCallbackFunc func) + { + mCallbackFunc = func; + } + + private: + std::string mCurrentStage; + std::string mCurrentOperation; + llcdCallbackFunc mCallbackFunc = nullptr; + }; + + class VHACDLogger : public VHACD::IVHACD::IUserLogger + { + void Log(const char* const msg) override + { + LL_INFOS("VHACD") << msg << LL_ENDL; + } + }; + +public: + + LLConvexDecompositionVHACD(); + virtual ~LLConvexDecompositionVHACD(); + + static bool isFunctional(); + static LLConvexDecomposition* getInstance(); + static LLCDResult initSystem(); + static LLCDResult initThread(); + static LLCDResult quitThread(); + static LLCDResult quitSystem(); + + void genDecomposition(int& decomp); + void deleteDecomposition(int decomp); + void bindDecomposition(int decomp); + + // Sets *paramsOut to the address of the LLCDParam array and returns + // the length of the array + int getParameters(const LLCDParam** paramsOut) + { + *paramsOut = mDecompParams.data(); + return narrow(mDecompParams.size()); + } + + int getStages(const LLCDStageData** stagesOut) + { + *stagesOut = mDecompStages.data(); + return narrow(mDecompStages.size()); + } + + // Set a parameter by name. Returns false if out of bounds or unsupported parameter + LLCDResult setParam(const char* name, float val); + LLCDResult setParam(const char* name, int val); + LLCDResult setParam(const char* name, bool val); + LLCDResult setMeshData( const LLCDMeshData* data, bool vertex_based ); + LLCDResult registerCallback(int stage, llcdCallbackFunc callback ); + + LLCDResult executeStage(int stage); + LLCDResult buildSingleHull(); + + int getNumHullsFromStage(int stage); + + LLCDResult getHullFromStage( int stage, int hull, LLCDHull* hullOut ); + LLCDResult getSingleHull( LLCDHull* hullOut ) ; + + // TODO: Implement lock of some kind to disallow this call if data not yet ready + LLCDResult getMeshFromStage( int stage, int hull, LLCDMeshData* meshDataOut); + LLCDResult getMeshFromHull( LLCDHull* hullIn, LLCDMeshData* meshOut ); + + // For visualizing convex hull shapes in the viewer physics shape display + LLCDResult generateSingleHullMeshFromMesh( LLCDMeshData* meshIn, LLCDMeshData* meshOut); + + /// Debug + void loadMeshData(const char* fileIn, LLCDMeshData** meshDataOut); + +private: + std::vector mDecompParams; + std::array mDecompStages; + + struct LLVHACDMesh + { + using vertex_type = VHACD::Vertex; + using index_type = VHACD::Triangle; + using vertex_array_type = std::vector; + using index_array_type = std::vector; + + LLVHACDMesh() = default; + LLVHACDMesh(const LLCDHull* hullIn) + { + if (hullIn) + { + from(hullIn); + } + }; + + LLVHACDMesh(const LLCDMeshData* meshIn, bool vertex_based) + { + if (meshIn) + { + from(meshIn, vertex_based); + } + }; + + void clear() + { + mVertices.clear(); + mIndices.clear(); + } + + void setVertices(const float* data, int num_vertices, int vertex_stride_bytes) + { + vertex_array_type vertices; + vertices.reserve(num_vertices); + + const int stride = vertex_stride_bytes / sizeof(float); + for (int i = 0; i < num_vertices; ++i) + { + vertices.emplace_back(data[i * stride + 0], + data[i * stride + 1], + data[i * stride + 2]); + } + + mVertices = std::move(vertices); + } + + void setIndices(const void* data, int num_indices, int index_stride_bytes, LLCDMeshData::IndexType type) + { + index_array_type indices; + indices.reserve(num_indices); + + if (type == LLCDMeshData::INT_16) + { + const U16* index_data = static_cast(data); + const int stride = index_stride_bytes / sizeof(U16); + for (int i = 0; i < num_indices; ++i) + { + indices.emplace_back(index_data[i * stride + 0], + index_data[i * stride + 1], + index_data[i * stride + 2]); + } + } + else + { + const U32* index_data = static_cast(data); + const int stride = index_stride_bytes / sizeof(U32); + for (int i = 0; i < num_indices; ++i) + { + indices.emplace_back(index_data[i * stride + 0], + index_data[i * stride + 1], + index_data[i * stride + 2]); + } + } + + mIndices = std::move(indices); + } + + LLCDResult from(const LLCDHull* hullIn) + { + clear(); + + if (!hullIn || !hullIn->mVertexBase || (hullIn->mNumVertices < 3) || (hullIn->mVertexStrideBytes != 12 && hullIn->mVertexStrideBytes != 16)) + { + return LLCD_INVALID_HULL_DATA; + } + + setVertices(hullIn->mVertexBase, hullIn->mNumVertices, hullIn->mVertexStrideBytes); + + return LLCD_OK; + } + + LLCDResult from(const LLCDMeshData* meshIn, bool vertex_based) + { + clear(); + + if (!meshIn || !meshIn->mVertexBase || (meshIn->mNumVertices < 3) || (meshIn->mVertexStrideBytes != 12 && meshIn->mVertexStrideBytes != 16)) + { + return LLCD_INVALID_MESH_DATA; + } + + if (!vertex_based && ((meshIn->mNumTriangles < 1) || !meshIn->mIndexBase)) + { + return LLCD_INVALID_MESH_DATA; + } + + setVertices(meshIn->mVertexBase, meshIn->mNumVertices, meshIn->mVertexStrideBytes); + if(!vertex_based) + { + setIndices(meshIn->mIndexBase, meshIn->mNumTriangles, meshIn->mIndexStrideBytes, meshIn->mIndexType); + } + + return LLCD_OK; + } + + vertex_array_type mVertices; + index_array_type mIndices; + }; + + struct LLConvexMesh + { + using vertex_type = glm::vec3; + using index_type = glm::u32vec3; + using vertex_array_type = std::vector; + using index_array_type = std::vector; + + LLConvexMesh() = default; + + void clear() + { + vertices.clear(); + indices.clear(); + } + + void setVertices(const std::vector& in_vertices) + { + vertices.clear(); + vertices.reserve(in_vertices.size()); + + for (const auto& vertex : in_vertices) + { + vertices.emplace_back(narrow(vertex.mX), narrow(vertex.mY), narrow(vertex.mZ)); + } + } + + void setIndices(const std::vector& in_indices) + { + indices.clear(); + indices.reserve(in_indices.size()); + + for (const auto& triangle : in_indices) + { + indices.emplace_back(narrow(triangle.mI0), narrow(triangle.mI1), narrow(triangle.mI2)); + } + } + + void to(LLCDHull* meshOut) const + { + meshOut->mVertexBase = (float*)vertices.data(); + meshOut->mVertexStrideBytes = sizeof(vertex_type); + meshOut->mNumVertices = (int)vertices.size(); + } + + void to(LLCDMeshData* meshOut) const + { + meshOut->mVertexBase = (float*)vertices.data(); + meshOut->mVertexStrideBytes = sizeof(vertex_type); + meshOut->mNumVertices = (int)vertices.size(); + + meshOut->mIndexType = LLCDMeshData::INT_32; + meshOut->mIndexBase = indices.data(); + meshOut->mIndexStrideBytes = sizeof(index_type); + meshOut->mNumTriangles = (int)indices.size(); + } + + vertex_array_type vertices; + index_array_type indices; + }; + + struct LLDecompData + { + LLVHACDMesh mSourceMesh; + LLConvexMesh mSingleHullMesh; + + std::vector mDecomposedHulls; + }; + + std::unordered_map mDecompData; + + LLDecompData* mBoundDecomp = nullptr; + + VHACD::IVHACD* mVHACD = nullptr; + VHACDCallback mVHACDCallback; + VHACDLogger mVHACDLogger; + VHACD::IVHACD::Parameters mVHACDParameters; + + LLConvexMesh mMeshFromHullData; + LLConvexMesh mSingleHullMeshFromMeshData; +}; + +#endif //LL_CONVEX_DECOMP_UTIL_VHACD_H diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index e13f0bbd96..9e90314a51 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -70,6 +70,12 @@ target_link_libraries(llprimitive ll::glm ) +if (TARGET llconvexdecomposition) + target_link_libraries(llprimitive + llconvexdecomposition + ) +endif () + #add unit tests if (LL_TESTS) INCLUDE(LLAddBuildTest) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 00ef79ce7f..8055bffd32 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1296,10 +1296,10 @@ LLModel::weight_list& LLModel::getJointInfluences(const LLVector3& pos) } void LLModel::setConvexHullDecomposition( - const LLModel::convex_hull_decomposition& decomp) + const LLModel::convex_hull_decomposition& decomp, const std::vector& decomp_mesh) { mPhysics.mHull = decomp; - mPhysics.mMesh.clear(); + mPhysics.mMesh = decomp_mesh; updateHullCenters(); } diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 6501b3dc50..ac88af18f0 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -305,7 +305,8 @@ public: S32 mDecompID; void setConvexHullDecomposition( - const convex_hull_decomposition& decomp); + const convex_hull_decomposition& decomp, + const std::vector& decomp_mesh); void updateHullCenters(); LLVector3 mCenterOfHullCenters; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index c727d5ae57..0f7670a57a 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1709,10 +1709,6 @@ if (WINDOWS) list(APPEND viewer_SOURCE_FILES ${viewer_INSTALLER_FILES}) endif (WINDOWS) -if (HAVOK OR HAVOK_TPV) - set(LLSTARTUP_COMPILE_FLAGS "${LLSTARTUP_COMPILE_FLAGS} -DLL_HAVOK") -endif (HAVOK OR HAVOK_TPV) - if( DEFINED LLSTARTUP_COMPILE_FLAGS ) # progress view disables/enables icons based on available packages set_source_files_properties(llprogressview.cpp PROPERTIES COMPILE_FLAGS "${LLSTARTUP_COMPILE_FLAGS}") @@ -2029,6 +2025,10 @@ if( TARGET ll::nvapi ) target_link_libraries(${VIEWER_BINARY_NAME} ll::nvapi ) endif() +if ( TARGET llconvexdecomposition ) + target_link_libraries(${VIEWER_BINARY_NAME} llconvexdecomposition ) +endif () + set(ARTWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH "Path to artwork files.") diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index f76f39222b..ef29be8200 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -1035,8 +1035,13 @@ void LLFloaterModelPreview::onPhysicsStageExecute(LLUICtrl* ctrl, void* data) gMeshRepo.mDecompThread->submitRequest(request); } } - - if (stage == "Decompose") + if (stage == "Analyze") + { + sInstance->setStatusMessage(sInstance->getString("decomposing")); + sInstance->childSetVisible("Analyze", false); + sInstance->childSetVisible("analyze_cancel", true); + } + else if (stage == "Decompose") { sInstance->setStatusMessage(sInstance->getString("decomposing")); sInstance->childSetVisible("Decompose", false); @@ -1137,6 +1142,7 @@ void LLFloaterModelPreview::initDecompControls() childSetCommitCallback("simplify_cancel", onPhysicsStageCancel, NULL); childSetCommitCallback("decompose_cancel", onPhysicsStageCancel, NULL); + childSetCommitCallback("analyze_cancel", onPhysicsStageCancel, NULL); childSetCommitCallback("physics_lod_combo", onPhysicsUseLOD, NULL); childSetCommitCallback("physics_browse", onPhysicsBrowse, NULL); @@ -2018,7 +2024,7 @@ void LLFloaterModelPreview::DecompRequest::completed() { //called from the main thread if (mContinue) { - mModel->setConvexHullDecomposition(mHull); + mModel->setConvexHullDecomposition(mHull, mHullMesh); if (sInstance) { diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index e0649e1d88..d71f368232 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -2626,7 +2626,16 @@ void LLModelPreview::updateStatusMessages() //enable = enable && !use_hull && fmp->childGetValue("physics_optimize").asBoolean(); //enable/disable "analysis" UI - LLPanel* panel = fmp->getChild("physics analysis"); +#if LL_HAVOK + LLPanel* panel = fmp->getChild("physics simplification"); + panel->setVisible(true); + + panel = fmp->getChild("physics analysis havok"); + panel->setVisible(true); +#else + LLPanel* panel = fmp->getChild("physics analysis vhacd"); + panel->setVisible(true); +#endif LLView* child = panel->getFirstChild(); while (child) { @@ -2650,6 +2659,8 @@ void LLModelPreview::updateStatusMessages() fmp->childSetVisible("simplify_cancel", false); fmp->childSetVisible("Decompose", true); fmp->childSetVisible("decompose_cancel", false); + fmp->childSetVisible("Analyze", true); + fmp->childSetVisible("analyze_cancel", false); if (phys_hulls > 0) { @@ -2659,6 +2670,7 @@ void LLModelPreview::updateStatusMessages() if (phys_tris || phys_hulls > 0) { fmp->childEnable("Decompose"); + fmp->childEnable("Analyze"); } } else diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index d72d428c08..fb9e5fd0a9 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -1070,7 +1070,7 @@ void LLViewerObjectList::fetchObjectCostsCoro(std::string url) if (diff.empty()) { - LL_INFOS() << "No outstanding object IDs to request. Pending count: " << mPendingObjectCost.size() << LL_ENDL; + LL_DEBUGS() << "No outstanding object IDs to request. Pending count: " << mPendingObjectCost.size() << LL_ENDL; return; } @@ -1205,7 +1205,7 @@ void LLViewerObjectList::fetchPhisicsFlagsCoro(std::string url) if (idList.size() < 1) { - LL_INFOS() << "No outstanding object physics flags to request." << LL_ENDL; + LL_DEBUGS() << "No outstanding object physics flags to request." << LL_ENDL; return; } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index f11d687840..39e9de0980 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -40,7 +40,7 @@ Simplifying... TBD One or more textures in this model were scaled to be within the allowed limits. - + Skinning disabled due to too many joints: [JOINTS], maximum: [MAX] Rigged to unrecognized joint name [NAME] @@ -807,7 +807,7 @@ help_topic="upload_model_physics" label="Physics" name="physics_panel"> - + --> - + - + + + Step 2: Convert to hulls (optional) + + + Fill Mode: + + + Resolution: + + + Hulls per Mesh: + + + Vertices per hull: + + + Error tolerance: + + + + + + +