diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 8d7d201be1..7d5cbee82a 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -1971,6 +1971,8 @@ LLVolume::LLVolume(const LLVolumeParams ¶ms, const F32 detail, const bool ge mNumHullIndices = 0; // set defaults + mSculptValidationCache.fill(LLSculptValidationState::Unvalidated); + if (mParams.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE) { mPathp = new LLDynamicPath(); @@ -1998,6 +2000,13 @@ void LLVolume::resizePath(S32 length) setDirty(); } +void LLVolume::setDirty() +{ + mPathp->setDirty(); + mProfilep->setDirty(); + mSculptValidationCache.fill(LLSculptValidationState::Unvalidated); +} + void LLVolume::regen() { generate(); @@ -3158,10 +3167,100 @@ S32 sculpt_sides(F32 detail) } } +static bool validate_sculpt_geometry(const LLVolume* volume) +{ + if (!volume || volume->getNumVolumeFaces() == 0) + { + return true; + } + + // Validate each face in the sculpt + for (S32 face_idx = 0; face_idx < volume->getNumVolumeFaces(); ++face_idx) + { + const LLVolumeFace& face = volume->getVolumeFace(face_idx); + + // Skip validation for degenerate faces + if (face.mNumVertices < 3 || face.mNumIndices < 3) + { + continue; + } + + // Calculate 'bounding' surface + LLVector4a extent_size; + extent_size.setSub(face.mExtents[1], face.mExtents[0]); + + F32 width = extent_size[0]; + F32 height = extent_size[1]; + F32 depth = extent_size[2]; + + F32 bounding_surface = width * height + width * depth + height * depth; + bounding_surface *= 2; + + if (!llfinite(bounding_surface) || bounding_surface <= FLT_EPSILON) + { + return false; + } + + // Calculate total surface area of all triangles + F32 total_triangle_area = 0.0f; + S32 num_triangles = face.mNumIndices / 3; + + for (S32 i = 0; i < num_triangles; ++i) + { + S32 idx_base = i * 3; + U16 idx0 = face.mIndices[idx_base]; + U16 idx1 = face.mIndices[idx_base + 1]; + U16 idx2 = face.mIndices[idx_base + 2]; + + // Validate indices + if (idx0 >= face.mNumVertices || idx1 >= face.mNumVertices || idx2 >= face.mNumVertices) + { + continue; + } + + const LLVector4a& v0 = face.mPositions[idx0]; + const LLVector4a& v1 = face.mPositions[idx1]; + const LLVector4a& v2 = face.mPositions[idx2]; + + // Calculate triangle area + LLVector4a edge1, edge2, cross; + edge1.setSub(v1, v0); + edge2.setSub(v2, v0); + cross.setCross3(edge1, edge2); + + F32 triangle_area = cross.getLength3().getF32() * 0.5f; + total_triangle_area += triangle_area; + } + + if (!llfinite(total_triangle_area)) + { + return false; + } + + // The idea here is that the total area of a sculpt is normally comparable + // to the area of a bounding box. But a random overlapping collection of + // triangles has a significantly larger area than a sculpt normally has. + F32 area_ratio = total_triangle_area / bounding_surface; + constexpr F32 MAX_AREA_TO_AREA_RATIO = 16.0f; + if (area_ratio > MAX_AREA_TO_AREA_RATIO) + { + LL_DEBUGS("LLVOLUME") << "Sculpt rejected: excessive triangle area." + << " Face index: " << face_idx + << " Total triangle area: " << total_triangle_area << " sq m" + << " Bounding surface area: " << bounding_surface << " sq m" + << " Ratio: " << area_ratio + << " Triangle count: " << num_triangles + << " Bounds: [" << width << " x " << height << " x " << depth << "]" << LL_ENDL; + return false; + } + } + + return true; +} // determine the number of vertices in both s and t direction for this sculpt -void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t) +static void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t) { // this code has the following properties: // 1) the aspect ratio of the mesh is as close as possible to the ratio of the map @@ -3271,10 +3370,62 @@ void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, mSculptLevel = sculpt_level; - // Delete any existing faces so that they get regenerated - mVolumeFaces.clear(); + LLSculptValidationState cached_state = LLSculptValidationState::Valid; + if (!data_is_empty && (sculpt_level >= 0) && sculpt_level < SCULPT_CACHE_SIZE) + { + // validation might be expensive, so we do it only once per lod. + cached_state = mSculptValidationCache[sculpt_level]; + } - createVolumeFaces(); + switch (cached_state) + { + case LLSculptValidationState::Valid: + { + // Delete any existing faces, then regenerate + mVolumeFaces.clear(); + createVolumeFaces(); + break; + } + case LLSculptValidationState::Invalid: + { + // Invalid geometry + // Regenerate mesh with an empty placeholder + mVolumeFaces.clear(); + sculptGenerateEmptyPlaceholder(); + createVolumeFaces(); + break; + } + case LLSculptValidationState::Unvalidated: + { + // First time at this LOD + mVolumeFaces.clear(); + createVolumeFaces(); + + bool valid_geometry = true; + + // Todo: can this be backed into createVolumeFaces? + // Or calculated without having to call createVolumeFaces first? + // Todo 2: should the same be done for meshes? + valid_geometry = validate_sculpt_geometry(this); + mSculptValidationCache[sculpt_level] = valid_geometry ? LLSculptValidationState::Valid : LLSculptValidationState::Invalid; + + if (!valid_geometry) + { + LL_WARNS("LLVOLUME") << "Sculpt failed geometry validation - either invalid image or malicious. " + << "Replacing with placeholder. Image: " << mParams.getSculptID() << LL_ENDL; + + // Clear the malicious faces + mVolumeFaces.clear(); + + // Regenerate mesh with empty placeholder + sculptGenerateEmptyPlaceholder(); + + // Recreate faces with placeholder geometry + createVolumeFaces(); + } + break; + } + } //switch (cached_state) } diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h index 3496928f7b..86a7bf0e6a 100644 --- a/indra/llmath/llvolume.h +++ b/indra/llmath/llvolume.h @@ -60,6 +60,8 @@ class LLVolumeOctree; #include "llalignedarray.h" #include "llrigginginfo.h" +#include + //============================================================================ constexpr S32 MIN_DETAIL_FACES = 6; @@ -1040,7 +1042,7 @@ class LLVolume : public LLRefCount const LLVector4a& getMeshPt(const U32 i) const { return mMesh[i]; } - void setDirty() { mPathp->setDirty(); mProfilep->setDirty(); } + void setDirty(); void regen(); void genTangents(S32 face); @@ -1131,6 +1133,11 @@ class LLVolume : public LLRefCount bool mIsMeshAssetLoaded; bool mIsMeshAssetUnavaliable; + // Cache for sculpt geometry validation results + static constexpr S32 SCULPT_CACHE_SIZE = 6; // 0 to 5 inclusive + enum class LLSculptValidationState : S8 { Unvalidated, Valid, Invalid }; + std::array mSculptValidationCache; + const LLVolumeParams mParams; LLPath *mPathp; LLProfile *mProfilep;