Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
159 changes: 155 additions & 4 deletions indra/llmath/llvolume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,8 @@ LLVolume::LLVolume(const LLVolumeParams &params, 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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}


Expand Down
9 changes: 8 additions & 1 deletion indra/llmath/llvolume.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class LLVolumeOctree;
#include "llalignedarray.h"
#include "llrigginginfo.h"

#include <array>

//============================================================================

constexpr S32 MIN_DETAIL_FACES = 6;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<LLSculptValidationState, SCULPT_CACHE_SIZE> mSculptValidationCache;
Comment thread
akleshchev marked this conversation as resolved.

const LLVolumeParams mParams;
LLPath *mPathp;
LLProfile *mProfilep;
Expand Down
Loading