summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/CMakeLists.txt3
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/llfloatermodelpreview.cpp31
-rw-r--r--indra/newview/llfloatermodelpreview.h4
-rw-r--r--indra/newview/llkeyconflict.cpp8
-rw-r--r--indra/newview/llmodelpreview.cpp649
-rw-r--r--indra/newview/llmodelpreview.h26
-rw-r--r--indra/newview/llviewerinput.cpp4
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml78
9 files changed, 779 insertions, 26 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 68b5969ff1..4c237c5ef3 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -31,6 +31,7 @@ include(LLInventory)
include(LLKDU)
include(LLLogin)
include(LLMath)
+include(LLMeshOptimizer)
include(LLMessage)
include(LLPhysicsExtensions)
include(LLPlugin)
@@ -77,6 +78,7 @@ include_directories(
${LLKDU_INCLUDE_DIRS}
${LLINVENTORY_INCLUDE_DIRS}
${LLMATH_INCLUDE_DIRS}
+ ${LLMESHOPTIMIZER_INCLUDE_DIRS}
${LLMESSAGE_INCLUDE_DIRS}
${LLPLUGIN_INCLUDE_DIRS}
${LLPRIMITIVE_INCLUDE_DIRS}
@@ -2027,6 +2029,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}
${LLCHARACTER_LIBRARIES}
${LLIMAGE_LIBRARIES}
${LLINVENTORY_LIBRARIES}
+ ${LLMESHOPTIMIZER_LIBRARIES}
${LLMESSAGE_LIBRARIES}
${LLPLUGIN_LIBRARIES}
${LLPRIMITIVE_LIBRARIES}
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index f186cd8874..f22d756da3 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-6.4.24
+6.5.0
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index d9edd4dc30..6795ea8f53 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -136,10 +136,10 @@ mAvatarTabIndex(0)
mStatusLock = new LLMutex();
mModelPreview = NULL;
- mLODMode[LLModel::LOD_HIGH] = 0;
+ mLODMode[LLModel::LOD_HIGH] = LLModelPreview::LOD_FROM_FILE;
for (U32 i = 0; i < LLModel::LOD_HIGH; i++)
{
- mLODMode[i] = 1;
+ mLODMode[i] = LLModelPreview::MESH_OPTIMIZER_AUTO;
}
}
@@ -722,7 +722,23 @@ void LLFloaterModelPreview::onAutoFillCommit(LLUICtrl* ctrl, void* userdata)
void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)
{
- mModelPreview->onLODParamCommit(lod, enforce_tri_limit);
+ LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[lod]);
+ S32 mode = lod_source_combo->getCurrentIndex();
+ switch (mode)
+ {
+ case LLModelPreview::GENERATE:
+ mModelPreview->onLODGenerateParamCommit(lod, enforce_tri_limit);
+ break;
+ case LLModelPreview::MESH_OPTIMIZER_AUTO:
+ case LLModelPreview::MESH_OPTIMIZER:
+ case LLModelPreview::MESH_OPTIMIZER_SLOPPY:
+ case LLModelPreview::MESH_OPTIMIZER_COMBINE:
+ mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode);
+ break;
+ default:
+ LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL;
+ break;
+ }
//refresh LoDs that reference this one
for (S32 i = lod - 1; i >= 0; --i)
@@ -1721,7 +1737,12 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod)
refresh();
LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[lod]);
- if (lod_source_combo->getCurrentIndex() == LLModelPreview::GENERATE)
+ S32 index = lod_source_combo->getCurrentIndex();
+ if (index == LLModelPreview::GENERATE
+ || index == LLModelPreview::MESH_OPTIMIZER_AUTO
+ || index == LLModelPreview::MESH_OPTIMIZER
+ || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY
+ || index == LLModelPreview::MESH_OPTIMIZER_COMBINE)
{ //rebuild LoD to update triangle counts
onLODParamCommit(lod, true);
}
@@ -1752,7 +1773,7 @@ void LLFloaterModelPreview::resetUploadOptions()
getChild<LLComboBox>("lod_source_" + lod_name[NUM_LOD - 1])->setCurrentByIndex(LLModelPreview::LOD_FROM_FILE);
for (S32 lod = 0; lod < NUM_LOD - 1; ++lod)
{
- getChild<LLComboBox>("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::GENERATE);
+ getChild<LLComboBox>("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::MESH_OPTIMIZER_AUTO);
childSetValue("lod_file_" + lod_name[lod], "");
}
diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h
index 8a01b0c307..51f9b3a0e2 100644
--- a/indra/newview/llfloatermodelpreview.h
+++ b/indra/newview/llfloatermodelpreview.h
@@ -196,9 +196,7 @@ protected:
std::map<std::string, bool> mViewOptionDisabled;
//store which lod mode each LOD is using
- // 0 - load from file
- // 1 - auto generate
- // 2 - use LoD above
+ // See eLoDMode
S32 mLODMode[4];
LLMutex* mStatusLock;
diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp
index b6107eeedf..d7a17b237e 100644
--- a/indra/newview/llkeyconflict.cpp
+++ b/indra/newview/llkeyconflict.cpp
@@ -730,13 +730,19 @@ void LLKeyConflictHandler::resetToDefault(const std::string &control_name, U32 i
{
return;
}
+ LLKeyConflict &type_data = mControlsMap[control_name];
+ if (!type_data.mAssignable)
+ {
+ return;
+ }
LLKeyData data = getDefaultControl(control_name, index);
- if (data != mControlsMap[control_name].getKeyData(index))
+ if (data != type_data.getKeyData(index))
{
// reset controls that might have been switched to our current control
removeConflicts(data, mControlsMap[control_name].mConflictMask);
mControlsMap[control_name].setKeyData(data, index);
+ mHasUnsavedChanges = true;
}
}
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index a9e80ab5da..b17aa960ce 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -41,6 +41,7 @@
#include "lliconctrl.h"
#include "llmatrix4a.h"
#include "llmeshrepository.h"
+#include "llmeshoptimizer.h"
#include "llrender.h"
#include "llsdutil_math.h"
#include "llskinningutil.h"
@@ -193,6 +194,7 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp)
mPreviewLOD = 0;
mModelLoader = NULL;
mMaxTriangleLimit = 0;
+ mMinTriangleLimit = 0;
mDirty = false;
mGenLOD = false;
mLoading = false;
@@ -1339,8 +1341,9 @@ void LLModelPreview::restoreNormals()
updateStatusMessages();
}
-void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_limit)
+void LLModelPreview::genGlodLODs(S32 which_lod, U32 decimation, bool enforce_tri_limit)
{
+ LL_INFOS() << "Generating lod " << which_lod << " using glod" << LL_ENDL;
// Allow LoD from -1 to LLModel::LOD_PHYSICS
if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1)
{
@@ -1403,7 +1406,7 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim
U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
- U32 lod_mode = 0;
+ U32 lod_mode = LIMIT_TRIANGLES;
F32 lod_error_threshold = 0;
@@ -1424,7 +1427,7 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim
mRequestedLoDMode[which_lod] = lod_mode;
}
- if (lod_mode == 0)
+ if (lod_mode == LIMIT_TRIANGLES)
{
lod_mode = GLOD_TRIANGLE_BUDGET;
@@ -1506,6 +1509,7 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim
}
mMaxTriangleLimit = base_triangle_count;
+ mMinTriangleLimit = mBaseModel.size();
for (S32 lod = start; lod >= end; --lod)
{
@@ -1539,7 +1543,7 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim
U32 actual_verts = 0;
U32 submeshes = 0;
- mRequestedTriangleCount[lod] = (S32)((F32)triangle_count / triangle_ratio);
+ mRequestedTriangleCount[lod] = llmax(mMinTriangleLimit, (S32)((F32)triangle_count / triangle_ratio));
mRequestedErrorThreshold[lod] = lod_error_threshold;
glodGroupParameteri(mGroup, GLOD_ADAPT_MODE, lod_mode);
@@ -1704,6 +1708,615 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim
}
}
+// Runs per object, but likely it is a better way to run per model+submodels
+// returns a ratio of base model indices to resulting indices
+// returns -1 in case of failure
+F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, bool sloppy)
+{
+ // Figure out buffer size
+ S32 size_indices = 0;
+ S32 size_vertices = 0;
+
+ for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx)
+ {
+ const LLVolumeFace &face = base_model->getVolumeFace(face_idx);
+ size_indices += face.mNumIndices;
+ size_vertices += face.mNumVertices;
+ }
+
+ // Allocate buffers, note that we are using U32 buffer instead of U16
+ U32* combined_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32));
+ U32* output_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32));
+
+ // extra space for normals and text coords
+ S32 tc_bytes_size = ((size_vertices * sizeof(LLVector2)) + 0xF) & ~0xF;
+ LLVector4a* combined_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size);
+ LLVector4a* combined_normals = combined_positions + size_vertices;
+ LLVector2* combined_tex_coords = (LLVector2*)(combined_normals + size_vertices);
+
+ // copy indices and vertices into new buffers
+ S32 combined_positions_shift = 0;
+ S32 indices_idx_shift = 0;
+ S32 combined_indices_shift = 0;
+ for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx)
+ {
+ const LLVolumeFace &face = base_model->getVolumeFace(face_idx);
+
+ // vertices
+ S32 copy_bytes = face.mNumVertices * sizeof(LLVector4a);
+ LLVector4a::memcpyNonAliased16((F32*)(combined_positions + combined_positions_shift), (F32*)face.mPositions, copy_bytes);
+
+ // normals
+ LLVector4a::memcpyNonAliased16((F32*)(combined_normals + combined_positions_shift), (F32*)face.mNormals, copy_bytes);
+
+ // tex coords
+ copy_bytes = (face.mNumVertices * sizeof(LLVector2) + 0xF) & ~0xF;
+ LLVector4a::memcpyNonAliased16((F32*)(combined_tex_coords + combined_positions_shift), (F32*)face.mTexCoords, copy_bytes);
+
+ combined_positions_shift += face.mNumVertices;
+
+ // indices, sadly can't do dumb memcpy for indices, need to adjust each value
+ for (S32 i = 0; i < face.mNumIndices; ++i)
+ {
+ U16 idx = face.mIndices[i];
+
+ combined_indices[combined_indices_shift] = idx + indices_idx_shift;
+ combined_indices_shift++;
+ }
+ indices_idx_shift += face.mNumVertices;
+ }
+
+ // Now that we have buffers, optimize
+ S32 target_indices = 0;
+ F32 result_code = 0; // how far from original the model is, 1 == 100%
+ S32 new_indices = 0;
+
+ target_indices = llmax(3, llfloor(size_indices / indices_decimator)); // leave at least one triangle
+ new_indices = LLMeshOptimizer::simplifyU32(
+ output_indices,
+ combined_indices,
+ size_indices,
+ combined_positions,
+ size_vertices,
+ LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX],
+ target_indices,
+ error_threshold,
+ sloppy,
+ &result_code);
+
+
+ if (result_code < 0)
+ {
+ LL_WARNS() << "Negative result code from meshoptimizer for model " << target_model->mLabel
+ << " target Indices: " << target_indices
+ << " new Indices: " << new_indices
+ << " original count: " << size_indices << LL_ENDL;
+ }
+
+ // repack back into individual faces
+
+ LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size);
+ LLVector4a* buffer_normals = buffer_positions + size_vertices;
+ LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices);
+ U16* buffer_indices = (U16*)ll_aligned_malloc_16(U16_MAX * sizeof(U16));
+ S32* old_to_new_positions_map = new S32[size_vertices];
+
+ S32 buf_positions_copied = 0;
+ S32 buf_indices_copied = 0;
+ indices_idx_shift = 0;
+
+ // Crude method to copy indices back into face
+ for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx)
+ {
+ const LLVolumeFace &face = base_model->getVolumeFace(face_idx);
+
+ // reset data for new run
+ buf_positions_copied = 0;
+ buf_indices_copied = 0;
+ bool copy_triangle = false;
+ S32 range = indices_idx_shift + face.mNumVertices;
+
+ for (S32 i = 0; i < size_vertices; i++)
+ {
+ old_to_new_positions_map[i] = -1;
+ }
+
+ // Copy relevant indices and vertices
+ for (S32 i = 0; i < new_indices; ++i)
+ {
+ U32 idx = output_indices[i];
+
+ if ((i % 3) == 0)
+ {
+ copy_triangle = idx >= indices_idx_shift && idx < range;
+ }
+
+ if (copy_triangle)
+ {
+ if (old_to_new_positions_map[idx] == -1)
+ {
+ // New position, need to copy it
+ // Validate size
+ if (buf_positions_copied >= U16_MAX)
+ {
+ // Normally this shouldn't happen since the whole point is to reduce amount of vertices
+ // but it might happen if user tries to run optimization with too large triangle or error value
+ // so fallback to 'per face' mode or verify requested limits and copy base model as is.
+ LL_WARNS() << "Over triangle limit. Failed to optimize in 'per object' mode, falling back to per face variant for"
+ << " model " << target_model->mLabel
+ << " target Indices: " << target_indices
+ << " new Indices: " << new_indices
+ << " original count: " << size_indices
+ << " error treshold: " << error_threshold
+ << LL_ENDL;
+ return -1;
+ }
+
+ // Copy vertice, normals, tcs
+ buffer_positions[buf_positions_copied] = combined_positions[idx];
+ buffer_normals[buf_positions_copied] = combined_normals[idx];
+ buffer_tex_coords[buf_positions_copied] = combined_tex_coords[idx];
+
+ old_to_new_positions_map[idx] = buf_positions_copied;
+
+ buffer_indices[buf_indices_copied] = (U16)buf_positions_copied;
+ buf_positions_copied++;
+ }
+ else
+ {
+ // existing position
+ buffer_indices[buf_indices_copied] = (U16)old_to_new_positions_map[idx];
+ }
+ buf_indices_copied++;
+ }
+ }
+
+ if (buf_positions_copied >= U16_MAX)
+ {
+ break;
+ }
+
+ LLVolumeFace &new_face = target_model->getVolumeFace(face_idx);
+ //new_face = face; //temp
+
+ if (buf_indices_copied < 3)
+ {
+ // face was optimized away
+ new_face.resizeIndices(3);
+ new_face.resizeVertices(1);
+ memset(new_face.mIndices, 0, sizeof(U16) * 3);
+ new_face.mPositions[0].clear(); // set first vertice to 0
+ new_face.mNormals[0].clear();
+ new_face.mTexCoords[0].setZero();
+ }
+ else
+ {
+ new_face.resizeIndices(buf_indices_copied);
+ new_face.resizeVertices(buf_positions_copied);
+
+ S32 idx_size = (buf_indices_copied * sizeof(U16) + 0xF) & ~0xF;
+ LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)buffer_indices, idx_size);
+
+ LLVector4a::memcpyNonAliased16((F32*)new_face.mPositions, (F32*)buffer_positions, buf_positions_copied * sizeof(LLVector4a));
+ LLVector4a::memcpyNonAliased16((F32*)new_face.mNormals, (F32*)buffer_normals, buf_positions_copied * sizeof(LLVector4a));
+
+ U32 tex_size = (buf_positions_copied * sizeof(LLVector2) + 0xF)&~0xF;
+ LLVector4a::memcpyNonAliased16((F32*)new_face.mTexCoords, (F32*)buffer_tex_coords, tex_size);
+ }
+
+ indices_idx_shift += face.mNumVertices;
+ }
+
+ delete[]old_to_new_positions_map;
+ ll_aligned_free<64>(combined_positions);
+ ll_aligned_free<64>(buffer_positions);
+ ll_aligned_free_32(output_indices);
+ ll_aligned_free_32(buffer_indices);
+ ll_aligned_free_32(combined_indices);
+
+ if (new_indices < 3)
+ {
+ // Model should have at least one visible triangle
+
+ if (!sloppy)
+ {
+ // Should only happen with sloppy
+ // non sloppy shouldn't be capable of optimizing mesh away
+ LL_WARNS() << "Failed to generate triangles"
+ << " model " << target_model->mLabel
+ << " target Indices: " << target_indices
+ << " new Indices: " << new_indices
+ << " original count: " << size_indices
+ << " error treshold: " << error_threshold
+ << LL_ENDL;
+ }
+
+ return -1;
+ }
+
+ return (F32)size_indices / (F32)new_indices;
+}
+
+F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_decimator, F32 error_threshold, bool sloppy)
+{
+ const LLVolumeFace &face = base_model->getVolumeFace(face_idx);
+ S32 size_indices = face.mNumIndices;
+ // todo: do not allocate per each face, add one large buffer somewhere
+ // faces have limited amount of indices
+ S32 size = (size_indices * sizeof(U16) + 0xF) & ~0xF;
+ U16* output = (U16*)ll_aligned_malloc_16(size);
+
+ S32 target_indices = 0;
+ F32 result_code = 0; // how far from original the model is, 1 == 100%
+ S32 new_indices = 0;
+
+ target_indices = llmax(3, llfloor(size_indices / indices_decimator)); // leave at least one triangle
+ new_indices = LLMeshOptimizer::simplify(
+ output,
+ face.mIndices,
+ size_indices,
+ face.mPositions,
+ face.mNumVertices,
+ LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX],
+ target_indices,
+ error_threshold,
+ sloppy,
+ &result_code);
+
+
+ if (result_code < 0)
+ {
+ LL_WARNS() << "Negative result code from meshoptimizer for face " << face_idx
+ << " of model " << target_model->mLabel
+ << " target Indices: " << target_indices
+ << " new Indices: " << new_indices
+ << " original count: " << size_indices
+ << " error treshold: " << error_threshold
+ << LL_ENDL;
+ }
+
+ LLVolumeFace &new_face = target_model->getVolumeFace(face_idx);
+
+ // Copy old values
+ new_face = face;
+
+
+ if (new_indices < 3)
+ {
+ if (!sloppy)
+ {
+ // meshopt_optimizeSloppy() can optimize triangles away even if target_indices is > 2,
+ // but optimize() isn't supposed to
+ LL_INFOS() << "No indices generated by meshoptimizer for face " << face_idx
+ << " of model " << target_model->mLabel
+ << " target Indices: " << target_indices
+ << " original count: " << size_indices
+ << " error treshold: " << error_threshold
+ << LL_ENDL;
+ }
+
+ // Face got optimized away
+ // Generate empty triangle
+ new_face.resizeIndices(3);
+ new_face.resizeVertices(1);
+ memset(new_face.mIndices, 0, sizeof(U16) * 3);
+ new_face.mPositions[0].clear(); // set first vertice to 0
+ new_face.mNormals[0].clear();
+ new_face.mTexCoords[0].setZero();
+ }
+ else
+ {
+ // Assign new values
+ new_face.resizeIndices(new_indices); // will wipe out mIndices, so new_face can't substitute output
+ S32 idx_size = (new_indices * sizeof(U16) + 0xF) & ~0xF;
+ LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)output, idx_size);
+
+ // clear unused values
+ new_face.optimize();
+ }
+
+ ll_aligned_free_16(output);
+
+ if (new_indices < 3)
+ {
+ // At least one triangle is needed
+ return -1;
+ }
+
+ return (F32)size_indices / (F32)new_indices;
+}
+
+void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit)
+{
+ LL_INFOS() << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL;
+ // Allow LoD from -1 to LLModel::LOD_PHYSICS
+ if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1)
+ {
+ std::ostringstream out;
+ out << "Invalid level of detail: " << which_lod;
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ assert(lod >= -1 && lod < LLModel::NUM_LODS);
+ return;
+ }
+
+ if (mBaseModel.empty())
+ {
+ return;
+ }
+
+ //get the triangle count for all base models
+ S32 base_triangle_count = 0;
+ for (S32 i = 0; i < mBaseModel.size(); ++i)
+ {
+ base_triangle_count += mBaseModel[i]->getNumTriangles();
+ }
+
+ // Urgh...
+ // TODO: add interface to mFMP to get error treshold or let mFMP write one into LLModelPreview
+ // We should not be accesing views from other class!
+ U32 lod_mode = LIMIT_TRIANGLES;
+ F32 indices_decimator = 0;
+ F32 triangle_limit = 0;
+ F32 lod_error_threshold = 1; //100%
+
+ // If requesting a single lod
+ if (which_lod > -1 && which_lod < NUM_LOD)
+ {
+ LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]);
+ if (iface)
+ {
+ lod_mode = iface->getFirstSelectedIndex();
+ }
+
+ if (lod_mode == LIMIT_TRIANGLES)
+ {
+ if (!enforce_tri_limit)
+ {
+ triangle_limit = base_triangle_count;
+ // reset to default value for this lod
+ F32 pw = pow((F32)decimation, (F32)(LLModel::LOD_HIGH - which_lod));
+
+ triangle_limit /= pw; //indices_ratio can be 1/pw
+ }
+ else
+ {
+
+ // UI spacifies limit for all models of single lod
+ triangle_limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger();
+
+ }
+ // meshoptimizer doesn't use triangle limit, it uses indices limit, so convert it to aproximate ratio
+ indices_decimator = (F32)base_triangle_count / triangle_limit;
+ }
+ else
+ {
+ lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal();
+ }
+ }
+ else
+ {
+ // we are genrating all lods and each lod will get own indices_decimator
+ indices_decimator = 1;
+ triangle_limit = base_triangle_count;
+ }
+
+ mMaxTriangleLimit = base_triangle_count;
+ mMinTriangleLimit = mBaseModel.size();
+
+ // TODO: Glod regenerates vertex buffer at this stage
+ // check why, it might be needed to regenerate buffer as well
+
+ // Build models
+
+ S32 start = LLModel::LOD_HIGH;
+ S32 end = 0;
+
+ if (which_lod != -1)
+ {
+ start = which_lod;
+ end = which_lod;
+ }
+
+ LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
+ if (shader)
+ {
+ shader->unbind();
+ }
+
+ for (S32 lod = start; lod >= end; --lod)
+ {
+ if (which_lod == -1)
+ {
+ // we are genrating all lods and each lod gets own indices_ratio
+ if (lod < start)
+ {
+ indices_decimator *= decimation;
+ triangle_limit /= decimation;
+ }
+ }
+
+ mRequestedTriangleCount[lod] = llmax(mMinTriangleLimit, (S32)triangle_limit);
+ mRequestedErrorThreshold[lod] = lod_error_threshold;
+ mRequestedLoDMode[lod] = lod_mode;
+
+ mModel[lod].clear();
+ mModel[lod].resize(mBaseModel.size());
+ mVertexBuffer[lod].clear();
+
+
+ for (U32 mdl_idx = 0; mdl_idx < mBaseModel.size(); ++mdl_idx)
+ {
+ LLModel* base = mBaseModel[mdl_idx];
+
+ LLVolumeParams volume_params;
+ volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+ mModel[lod][mdl_idx] = new LLModel(volume_params, 0.f);
+
+ std::string name = base->mLabel + getLodSuffix(lod);
+
+ mModel[lod][mdl_idx]->mLabel = name;
+ mModel[lod][mdl_idx]->mSubmodelID = base->mSubmodelID;
+ mModel[lod][mdl_idx]->setNumVolumeFaces(base->getNumVolumeFaces());
+
+ LLModel* target_model = mModel[lod][mdl_idx];
+
+ S32 model_meshopt_mode = meshopt_mode;
+
+ // Ideally this should run not per model,
+ // but combine all submodels with origin model as well
+ if (model_meshopt_mode == MESH_OPTIMIZER_COMBINE)
+ {
+ // Run meshoptimizer for each model/object, up to 8 faces in one model
+
+ // Ideally this should run not per model,
+ // but combine all submodels with origin model as well
+ F32 res = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
+ if (res < 0)
+ {
+ // U16 vertices overflow, shouldn't happen, but just in case
+ for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ {
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+ }
+ }
+ }
+
+ if (model_meshopt_mode == MESH_OPTIMIZER)
+ {
+ // Run meshoptimizer for each face
+ for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ {
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+ }
+ }
+
+ if (model_meshopt_mode == MESH_OPTIMIZER_SLOPPY)
+ {
+ // Run meshoptimizer for each face
+ for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ {
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true);
+ }
+ }
+
+ if (model_meshopt_mode == MESH_OPTIMIZER_AUTO)
+ {
+ F32 allowed_ratio_drift = 2.f;
+ F32 res_ratio = 0;
+ if (base->mHasGeneratedFaces)
+ {
+ res_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
+
+ if (res_ratio < 0)
+ {
+ // U16 vertices overflow, shouldn't happen, but just in case
+ for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ {
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+ }
+ LL_INFOS() << "Model " << target_model->getName()
+ << " lod " << which_lod
+ << " per model method overflow, defaulting to per face." << LL_ENDL;
+ }
+ else if (res_ratio * allowed_ratio_drift < indices_decimator)
+ {
+ // Try sloppy variant if normal one failed to simplify model enough.
+ res_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true);
+ LL_INFOS() << "Model " << target_model->getName()
+ << " lod " << which_lod
+ << " sloppily simplified using per model method." << LL_ENDL;
+
+
+ if (res_ratio < 0)
+ {
+ // Sloppy variant failed to generate triangles.
+ // Can happen with models that are too simple as is.
+ // Fallback to normal method.
+ genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
+ }
+ }
+ else
+ {
+ LL_INFOS() << "Model " << target_model->getName()
+ << " lod " << which_lod
+ << " simplified using per model method." << LL_ENDL;
+ }
+ }
+ else
+ {
+ for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ {
+ res_ratio = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+
+ if (res_ratio * allowed_ratio_drift < indices_decimator)
+ {
+ // normal method failed to sufficiently simplify, try sloppy
+ res_ratio = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true);
+ if (res_ratio < 0)
+ {
+ // Sloppy failed to generate triangles.
+ // Can happen with models that are too simple as is.
+ // Fallback to normal method.
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+ }
+ }
+ }
+ LL_INFOS() << "Model " << target_model->getName()
+ << " lod " << which_lod
+ << " simplified using per face methods." << LL_ENDL;
+ }
+ }
+
+ //blind copy skin weights and just take closest skin weight to point on
+ //decimated mesh for now (auto-generating LODs with skin weights is still a bit
+ //of an open problem).
+ target_model->mPosition = base->mPosition;
+ target_model->mSkinWeights = base->mSkinWeights;
+ target_model->mSkinInfo = base->mSkinInfo;
+
+ //copy material list
+ target_model->mMaterialList = base->mMaterialList;
+
+ if (!validate_model(target_model))
+ {
+ LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL;
+ }
+ }
+
+ //rebuild scene based on mBaseScene
+ mScene[lod].clear();
+ mScene[lod] = mBaseScene;
+
+ for (U32 i = 0; i < mBaseModel.size(); ++i)
+ {
+ LLModel* mdl = mBaseModel[i];
+ LLModel* target = mModel[lod][i];
+ if (target)
+ {
+ for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter)
+ {
+ for (U32 j = 0; j < iter->second.size(); ++j)
+ {
+ if (iter->second[j].mModel == mdl)
+ {
+ iter->second[j].mModel = target;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mResourceCost = calcResourceCost();
+
+ LLVertexBuffer::unbind();
+ if (shader)
+ {
+ shader->bind();
+ }
+}
+
void LLModelPreview::updateStatusMessages()
{
// bit mask values for physics errors. used to prevent overwrite of single line status
@@ -1821,6 +2434,7 @@ void LLModelPreview::updateStatusMessages()
if (mMaxTriangleLimit == 0)
{
mMaxTriangleLimit = total_tris[LLModel::LOD_HIGH];
+ mMinTriangleLimit = mUploadData.size();
}
mHasDegenerate = false;
@@ -2264,7 +2878,7 @@ void LLModelPreview::updateLodControls(S32 lod)
S32 lod_mode = lod_combo->getCurrentIndex();
if (lod_mode == LOD_FROM_FILE) // LoD from file
{
- fmp->mLODMode[lod] = 0;
+ fmp->mLODMode[lod] = LOD_FROM_FILE;
for (U32 i = 0; i < num_file_controls; ++i)
{
mFMP->childSetVisible(file_controls[i] + lod_name[lod], true);
@@ -2277,7 +2891,7 @@ void LLModelPreview::updateLodControls(S32 lod)
}
else if (lod_mode == USE_LOD_ABOVE) // use LoD above
{
- fmp->mLODMode[lod] = 2;
+ fmp->mLODMode[lod] = USE_LOD_ABOVE;
for (U32 i = 0; i < num_file_controls; ++i)
{
mFMP->childSetVisible(file_controls[i] + lod_name[lod], false);
@@ -2303,7 +2917,7 @@ void LLModelPreview::updateLodControls(S32 lod)
}
else // auto generate, the default case for all LoDs except High
{
- fmp->mLODMode[lod] = 1;
+ fmp->mLODMode[lod] = MESH_OPTIMIZER_AUTO;
//don't actually regenerate lod when refreshing UI
mLODFrozen = true;
@@ -2323,6 +2937,7 @@ void LLModelPreview::updateLodControls(S32 lod)
LLSpinCtrl* limit = mFMP->getChild<LLSpinCtrl>("lod_triangle_limit_" + lod_name[lod]);
limit->setMaxValue(mMaxTriangleLimit);
+ limit->setMinValue(mMinTriangleLimit);
limit->forceSetValue(mRequestedTriangleCount[lod]);
threshold->forceSetValue(mRequestedErrorThreshold[lod]);
@@ -2335,7 +2950,8 @@ void LLModelPreview::updateLodControls(S32 lod)
threshold->setVisible(false);
limit->setMaxValue(mMaxTriangleLimit);
- limit->setIncrement(mMaxTriangleLimit / 32);
+ limit->setMinValue(mMinTriangleLimit);
+ limit->setIncrement(llmax((U32)1, mMaxTriangleLimit / 32));
}
else
{
@@ -2957,7 +3573,7 @@ BOOL LLModelPreview::render()
{
genBuffers(-1, skin_weight);
//genBuffers(3);
- //genLODs();
+ //genGlodLODs();
}
if (!mModel[mPreviewLOD].empty())
@@ -3544,7 +4160,7 @@ bool LLModelPreview::lodQueryCallback()
{
S32 lod = preview->mLodsQuery.back();
preview->mLodsQuery.pop_back();
- preview->genLODs(lod);
+ preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER_AUTO);
if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH))
{
@@ -3559,11 +4175,20 @@ bool LLModelPreview::lodQueryCallback()
return true;
}
-void LLModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)
+void LLModelPreview::onLODGenerateParamCommit(S32 lod, bool enforce_tri_limit)
+{
+ if (!mLODFrozen)
+ {
+ genGlodLODs(lod, 3, enforce_tri_limit);
+ refresh();
+ }
+}
+
+void LLModelPreview::onLODMeshOptimizerParamCommit(S32 requested_lod, bool enforce_tri_limit, S32 mode)
{
if (!mLODFrozen)
{
- genLODs(lod, 3, enforce_tri_limit);
+ genMeshOptimizerLODs(requested_lod, mode, 3, enforce_tri_limit);
refresh();
}
}
diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h
index 3664a27a72..49032f0dbf 100644
--- a/indra/newview/llmodelpreview.h
+++ b/indra/newview/llmodelpreview.h
@@ -125,9 +125,19 @@ public:
{
LOD_FROM_FILE = 0,
GENERATE,
+ MESH_OPTIMIZER_AUTO, // automatically selects method based on model or face
+ MESH_OPTIMIZER_COMBINE,
+ MESH_OPTIMIZER,
+ MESH_OPTIMIZER_SLOPPY,
USE_LOD_ABOVE,
} eLoDMode;
+ typedef enum
+ {
+ LIMIT_TRIANGLES = 0,
+ LIMIT_ERROR_TRESHOLD,
+ } eLoDLimit;
+
public:
// Todo: model preview shouldn't need floater dependency, it
// should just expose data to floater, not control flaoter like it does
@@ -155,7 +165,8 @@ public:
void loadModelCallback(S32 lod);
bool lodsReady() { return !mGenLOD && mLodsQuery.empty(); }
void queryLODs() { mGenLOD = true; };
- void genLODs(S32 which_lod = -1, U32 decimation = 3, bool enforce_tri_limit = false);
+ void genGlodLODs(S32 which_lod = -1, U32 decimation = 3, bool enforce_tri_limit = false);
+ void genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation = 3, bool enforce_tri_limit = false);
void generateNormals();
void restoreNormals();
U32 calcResourceCost();
@@ -166,7 +177,8 @@ public:
void updateStatusMessages();
void updateLodControls(S32 lod);
void clearGLODGroup();
- void onLODParamCommit(S32 lod, bool enforce_tri_limit);
+ void onLODGenerateParamCommit(S32 lod, bool enforce_tri_limit);
+ void onLODMeshOptimizerParamCommit(S32 lod, bool enforce_tri_limit, S32 mode);
void addEmptyFace(LLModel* pTarget);
const bool getModelPivot(void) const { return mHasPivot; }
@@ -218,6 +230,10 @@ private:
// Count amount of original models, excluding sub-models
static U32 countRootModels(LLModelLoader::model_list models);
+ // functions for meshoptimizer, return reached simplification ratio
+ F32 genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_ratio, F32 error_threshold, bool sloppy);
+ F32 genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_ratio, F32 error_threshold, bool sloppy);
+
protected:
friend class LLModelLoader;
friend class LLFloaterModelPreview;
@@ -280,8 +296,14 @@ protected:
U32 mGroup;
std::map<LLPointer<LLModel>, U32> mObject;
+
+ // Amount of triangles in original(base) model
U32 mMaxTriangleLimit;
+ // Minimum amount of allowed triangles in lod for spin cntrl.
+ // Leave at least one triangle per model.
+ S32 mMinTriangleLimit;
+
LLMeshUploadThread::instance_list mUploadData;
std::set<LLViewerFetchedTexture * > mTextureSet;
diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp
index c0eaa88f54..f269be035e 100644
--- a/indra/newview/llviewerinput.cpp
+++ b/indra/newview/llviewerinput.cpp
@@ -1070,7 +1070,7 @@ BOOL LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, cons
if (!function)
{
- LL_ERRS() << "Can't bind key to function " << function_name << ", no function with this name found" << LL_ENDL;
+ LL_WARNS() << "Can't bind key to function " << function_name << ", no function with this name found" << LL_ENDL;
return FALSE;
}
@@ -1112,7 +1112,7 @@ BOOL LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const
if (!function)
{
- LL_ERRS() << "Can't bind key to function " << function_name << ", no function with this name found" << LL_ENDL;
+ LL_WARNS() << "Can't bind mouse key to function " << function_name << ", no function with this name found" << LL_ENDL;
return FALSE;
}
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 7f863756eb..05990e28d8 100644
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -45,7 +45,21 @@
<string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string>
<string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string>
<string name="ModelLoaded">Model [MODEL_NAME] loaded</string>
+
<string name="IncompleteTC">Texture coordinates data is not complete.</string>
+ <string name="PositionNaN">Found NaN while loading position data from DAE-Model, invalid model.</string>
+ <string name="NormalsNaN">Found NaN while loading normals from DAE-Model, invalid model.</string>
+ <string name="NegativeScaleTrans">Negative scale detected, unsupported transform. domInstance_geometry: [LABEL]</string>
+ <string name="NegativeScaleNormTrans">Negative scale detected, unsupported post-normalization transform. domInstance_geometry: [LABEL]</string>
+ <string name="CantResolveGeometryUrl">Unable to resolve geometry URL.</string>
+ <string name="ParsingErrorBadElement">Bad element</string>
+ <string name="ParsingErrorCantParseScene">Scene could not be parsed</string>
+ <string name="ParsingErrorCorrupt">Error with dae - traditionally indicates a corrupt file.</string>
+ <string name="ParsingErrorNoController">Could not verify controller</string>
+ <string name="ParsingErrorNoDoc">Can't find internal doc</string>
+ <string name="ParsingErrorNoRoot">Document has no root</string>
+ <string name="ParsingErrorNoScene">Document has no visual_scene</string>
+ <string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string>
<panel
follows="top|left"
@@ -176,6 +190,22 @@
name="Generate"
label="Generate"
value="Generate" />
+ <item
+ name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
+ label="Simplify per face"
+ value="MeshOpt" />
+ <item
+ name="MeshOptSloppy"
+ label="Simplify sloppy"
+ value="MeshOptSloppy" />
</combo_box>
<line_editor
follows="left|top"
@@ -306,6 +336,22 @@
label="Generate"
value="Generate" />
<item
+ name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
+ label="Simplify per face"
+ value="MeshOpt" />
+ <item
+ name="MeshOptSloppy"
+ label="Simplify sloppy"
+ value="MeshOptSloppy" />
+ <item
name="Use LoD above"
label="Use LoD above"
value="Use LoD above" />
@@ -439,6 +485,22 @@
label="Generate"
value="Generate" />
<item
+ name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
+ label="Simplify per face"
+ value="MeshOpt" />
+ <item
+ name="MeshOptSloppy"
+ label="Simplify sloppy"
+ value="MeshOptSloppy" />
+ <item
name="Use LoD above"
label="Use LoD above"
value="Use LoD above" />
@@ -572,6 +634,22 @@
label="Generate"
value="Generate" />
<item
+ name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
+ label="Simplify per face"
+ value="MeshOpt" />
+ <item
+ name="MeshOptSloppy"
+ label="Simplify sloppy"
+ value="MeshOptSloppy" />
+ <item
name="Use LoD above"
label="Use LoD above"
value="Use LoD above" />