summaryrefslogtreecommitdiff
path: root/indra/newview/gltf/llgltfloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/gltf/llgltfloader.cpp')
-rw-r--r--indra/newview/gltf/llgltfloader.cpp719
1 files changed, 414 insertions, 305 deletions
diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp
index 7ae255e054..3019a12446 100644
--- a/indra/newview/gltf/llgltfloader.cpp
+++ b/indra/newview/gltf/llgltfloader.cpp
@@ -86,20 +86,23 @@ static const glm::mat4 coord_system_rotationxy(
0.f, 0.f, 0.f, 1.f
);
-LLGLTFLoader::LLGLTFLoader(std::string filename,
- S32 lod,
- LLModelLoader::load_callback_t load_cb,
- LLModelLoader::joint_lookup_func_t joint_lookup_func,
- LLModelLoader::texture_load_func_t texture_load_func,
- LLModelLoader::state_callback_t state_cb,
- void * opaque_userdata,
- JointTransformMap & jointTransformMap,
- JointNameSet & jointsFromNodes,
- std::map<std::string, std::string> &jointAliasMap,
- U32 maxJointsPerMesh,
- U32 modelLimit,
- std::vector<LLJointData> viewer_skeleton) //,
- //bool preprocess)
+static const S32 VERTICIES_LIMIT = USHRT_MAX - 2;
+
+LLGLTFLoader::LLGLTFLoader(std::string filename,
+ S32 lod,
+ LLModelLoader::load_callback_t load_cb,
+ LLModelLoader::joint_lookup_func_t joint_lookup_func,
+ LLModelLoader::texture_load_func_t texture_load_func,
+ LLModelLoader::state_callback_t state_cb,
+ void * opaque_userdata,
+ JointTransformMap & jointTransformMap,
+ JointNameSet & jointsFromNodes,
+ std::map<std::string, std::string, std::less<>> & jointAliasMap,
+ U32 maxJointsPerMesh,
+ U32 modelLimit,
+ U32 debugMode,
+ std::vector<LLJointData> viewer_skeleton) //,
+ //bool preprocess)
: LLModelLoader( filename,
lod,
load_cb,
@@ -110,9 +113,12 @@ LLGLTFLoader::LLGLTFLoader(std::string filename,
jointTransformMap,
jointsFromNodes,
jointAliasMap,
- maxJointsPerMesh )
- , mGeneratedModelLimit(modelLimit)
+ maxJointsPerMesh,
+ modelLimit,
+ debugMode)
, mViewerJointData(viewer_skeleton)
+ , mGltfLoaded(false)
+ , mApplyXYRotation(false)
{
}
@@ -120,11 +126,39 @@ LLGLTFLoader::~LLGLTFLoader() {}
bool LLGLTFLoader::OpenFile(const std::string &filename)
{
+ // Clear the material cache for new file
+ mMaterialCache.clear();
+
tinygltf::TinyGLTF loader;
std::string filename_lc(filename);
LLStringUtil::toLower(filename_lc);
- mGltfLoaded = mGLTFAsset.load(filename, false);
+ try
+ {
+ mGltfLoaded = mGLTFAsset.load(filename, false);
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS() << "Exception in LLModelLoader::run: " << e.what() << LL_ENDL;
+ LLSD args;
+ args["Message"] = "ParsingErrorException";
+ args["FILENAME"] = filename;
+ args["EXCEPTION"] = e.what();
+ mWarningsArray.append(args);
+ setLoadState(ERROR_PARSING);
+ return false;
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION("LLGLTFLoader");
+ LLSD args;
+ args["Message"] = "ParsingErrorException";
+ args["FILENAME"] = filename;
+ args["EXCEPTION"] = "Unknown exception";
+ mWarningsArray.append(args);
+ setLoadState(ERROR_PARSING);
+ return false;
+ }
if (!mGltfLoaded)
{
@@ -157,6 +191,7 @@ bool LLGLTFLoader::OpenFile(const std::string &filename)
void LLGLTFLoader::addModelToScene(
LLModel* pModel,
+ const std::string& model_name,
U32 submodel_limit,
const LLMatrix4& transformation,
const LLVolumeParams& volume_params,
@@ -175,12 +210,23 @@ void LLGLTFLoader::addModelToScene(
U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES;
if (face_limit < volume_faces)
{
+ LL_WARNS("GLTF_IMPORT") << "Model contains " << volume_faces
+ << " faces, exceeding the limit of " << face_limit << LL_ENDL;
+
+ LLSD args;
+ args["Message"] = "ModelTooManySubmodels";
+ args["MODEL_NAME"] = pModel->mLabel;
+ args["SUBMODEL_COUNT"] = static_cast<S32>(llfloor((F32)volume_faces / LL_SCULPT_MESH_MAX_FACES));
+ args["SUBMODEL_LIMIT"] = static_cast<S32>(submodel_limit);
+ mWarningsArray.append(args);
+
pModel->setNumVolumeFaces(face_limit);
}
LLVolume::face_list_t remainder;
std::vector<LLModel*> ready_models;
LLModel* current_model = pModel;
+
do
{
current_model->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder);
@@ -198,7 +244,26 @@ void LLGLTFLoader::addModelToScene(
LLModel* next = new LLModel(volume_params, 0.f);
next->ClearFacesAndMaterials();
next->mSubmodelID = ++submodelID;
- next->mLabel = pModel->mLabel + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod];
+
+ std::string instance_name = model_name;
+ if (next->mSubmodelID > 0)
+ {
+ instance_name += (char)((int)'a' + next->mSubmodelID);
+ }
+ // Check for duplicates and add copy suffix if needed
+ int duplicate_count = 0;
+ for (const auto& inst : mScene[transformation])
+ {
+ if (inst.mLabel == instance_name)
+ {
+ ++duplicate_count;
+ }
+ }
+ if (duplicate_count > 0) {
+ instance_name += "_copy_" + std::to_string(duplicate_count);
+ }
+ next->mLabel = instance_name;
+
next->getVolumeFaces() = remainder;
next->mNormalizedScale = current_model->mNormalizedScale;
next->mNormalizedTranslation = current_model->mNormalizedTranslation;
@@ -248,7 +313,13 @@ void LLGLTFLoader::addModelToScene(
materials[model->mMaterialList[i]] = LLImportMaterial();
}
}
- mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials));
+ // Keep base name for scene instance.
+ std::string instance_name = model->mLabel;
+ // Add suffix. Suffix is nessesary for model matching logic
+ // because sometimes higher lod can be used as a lower one, so models
+ // need unique names not just in scope of one lod, but across lods.
+ model->mLabel += lod_suffix[mLod];
+ mScene[transformation].push_back(LLModelInstance(model, instance_name, transformation, materials));
stretch_extents(model, transformation);
}
}
@@ -276,7 +347,7 @@ bool LLGLTFLoader::parseMeshes()
}
// Populate the joints from skins first.
- // There's not many skins - and you can pretty easily iterate through the nodes from that.
+ // Multiple meshes can share the same skin, so preparing skins beforehand.
for (S32 i = 0; i < mGLTFAsset.mSkins.size(); i++)
{
populateJointsFromSkin(i);
@@ -284,7 +355,9 @@ bool LLGLTFLoader::parseMeshes()
// Track how many times each mesh name has been used
std::map<std::string, S32> mesh_name_counts;
- U32 submodel_limit = mGLTFAsset.mNodes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mNodes.size() : 0;
+
+ // For now use mesh count, but might be better to do 'mNodes.size() - joints count'.
+ U32 submodel_limit = mGLTFAsset.mMeshes.size() > 0 ? mGeneratedModelLimit / (U32)mGLTFAsset.mMeshes.size() : 0;
// Check if we have scenes defined
if (!mGLTFAsset.mScenes.empty())
@@ -318,20 +391,7 @@ bool LLGLTFLoader::parseMeshes()
return false;
}
- // Check total model count against limit
- U32 total_models = static_cast<U32>(mModelList.size());
- if (total_models > mGeneratedModelLimit)
- {
- LL_WARNS("GLTF_IMPORT") << "Model contains " << total_models
- << " mesh parts, exceeding the limit of " << mGeneratedModelLimit << LL_ENDL;
-
- LLSD args;
- args["Message"] = "TooManyMeshParts";
- args["PART_COUNT"] = static_cast<S32>(total_models);
- args["LIMIT"] = static_cast<S32>(mGeneratedModelLimit);
- mWarningsArray.append(args);
- return false;
- }
+ checkGlobalJointUsage();
return true;
}
@@ -341,9 +401,9 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>
if (node_idx < 0 || node_idx >= static_cast<S32>(mGLTFAsset.mNodes.size()))
return;
- auto& node = mGLTFAsset.mNodes[node_idx];
+ const LL::GLTF::Node& node = mGLTFAsset.mNodes[node_idx];
- LL_INFOS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")"
+ LL_DEBUGS("GLTF_IMPORT") << "Processing node " << node_idx << " (" << node.mName << ")"
<< " - has mesh: " << (node.mMesh >= 0 ? "yes" : "no")
<< " - children: " << node.mChildren.size() << LL_ENDL;
@@ -354,10 +414,10 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>
material_map mats;
LLModel* pModel = new LLModel(volume_params, 0.f);
- auto& mesh = mGLTFAsset.mMeshes[node.mMesh];
+ const LL::GLTF::Mesh& mesh = mGLTFAsset.mMeshes[node.mMesh];
// Get base mesh name and track usage
- std::string base_name = mesh.mName;
+ std::string base_name = getLodlessLabel(mesh);
if (base_name.empty())
{
base_name = "mesh_" + std::to_string(node.mMesh);
@@ -365,7 +425,13 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>
S32 instance_count = mesh_name_counts[base_name]++;
- if (populateModelFromMesh(pModel, mesh, node, mats, instance_count) &&
+ // make name unique
+ if (instance_count > 0)
+ {
+ base_name = base_name + "_copy_" + std::to_string(instance_count);
+ }
+
+ if (populateModelFromMesh(pModel, base_name, mesh, node, mats) &&
(LLModel::NO_ERRORS == pModel->getStatus()) &&
validate_model(pModel))
{
@@ -413,7 +479,7 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map<std::string, S32>
mWarningsArray.append(args);
}
- addModelToScene(pModel, submodel_limit, transformation, volume_params, mats);
+ addModelToScene(pModel, base_name, submodel_limit, transformation, volume_params, mats);
mats.clear();
}
else
@@ -478,7 +544,7 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3
}
}
-bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx) const
+bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_idx, size_t gltf_joint_idx)
{
const std::string& legal_name = mJointNames[gltf_skin_idx][gltf_joint_idx];
if (legal_name.empty())
@@ -493,32 +559,155 @@ bool LLGLTFLoader::addJointToModelSkin(LLMeshSkinInfo& skin_info, S32 gltf_skin_
skin_info.mInvBindMatrix.push_back(mInverseBindMatrices[gltf_skin_idx][gltf_joint_idx]);
skin_info.mAlternateBindMatrix.push_back(mAlternateBindMatrices[gltf_skin_idx][gltf_joint_idx]);
+ // Track joint usage for this skin, for the sake of unused joints detection
+ mJointUsage[gltf_skin_idx][gltf_joint_idx]++;
+
return true;
}
-bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats, S32 instance_count)
+LLGLTFLoader::LLGLTFImportMaterial LLGLTFLoader::processMaterial(S32 material_index, S32 fallback_index)
{
- // Set the requested label for the floater display and uploading
- pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true);
+ // Check cache first
+ auto cached = mMaterialCache.find(material_index);
+ if (cached != mMaterialCache.end())
+ {
+ return cached->second;
+ }
+
+ LLImportMaterial impMat;
+ impMat.mDiffuseColor = LLColor4::white; // Default color
+
+ // Generate material name
+ std::string materialName = generateMaterialName(material_index, fallback_index);
+
+ // Process material if available
+ if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size())
+ {
+ LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index];
+
+ // Set diffuse color from base color factor
+ impMat.mDiffuseColor = LLColor4(
+ material->mPbrMetallicRoughness.mBaseColorFactor[0],
+ material->mPbrMetallicRoughness.mBaseColorFactor[1],
+ material->mPbrMetallicRoughness.mBaseColorFactor[2],
+ material->mPbrMetallicRoughness.mBaseColorFactor[3]
+ );
+
+ // Process base color texture if it exists
+ if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0)
+ {
+ S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex;
+ std::string filename = processTexture(texIndex, "base_color", material->mName);
+
+ if (!filename.empty())
+ {
+ impMat.mDiffuseMapFilename = filename;
+ impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName;
+
+ // Check if the texture is already loaded
+ S32 sourceIndex;
+ if (validateTextureIndex(texIndex, sourceIndex))
+ {
+ LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex];
+ if (image.mTexture.notNull())
+ {
+ impMat.setDiffuseMap(image.mTexture->getID());
+ LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL;
+ }
+ }
+ }
+ }
+ }
+
+ // Create cached material with both material and name
+ LLGLTFImportMaterial cachedMat(impMat, materialName);
+
+ // Cache the processed material
+ mMaterialCache[material_index] = cachedMat;
+ return cachedMat;
+}
+
+std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& texture_type, const std::string& material_name)
+{
+ S32 sourceIndex;
+ if (!validateTextureIndex(texture_index, sourceIndex))
+ return "";
+
+ LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex];
- // Create unique model name
- std::string base_name = mesh.mName;
- if (base_name.empty())
+ // Process URI-based textures
+ if (!image.mUri.empty())
{
- S32 mesh_index = static_cast<S32>(&mesh - &mGLTFAsset.mMeshes[0]);
- base_name = "mesh_" + std::to_string(mesh_index);
+ std::string filename = image.mUri;
+ size_t pos = filename.find_last_of("/\\");
+ if (pos != std::string::npos)
+ {
+ filename = filename.substr(pos + 1);
+ }
+
+ LL_INFOS("GLTF_IMPORT") << "Found texture: " << filename << " for material: " << material_name << LL_ENDL;
+
+ LLSD args;
+ args["Message"] = "TextureFound";
+ args["TEXTURE_NAME"] = filename;
+ args["MATERIAL_NAME"] = material_name;
+ mWarningsArray.append(args);
+
+ return filename;
+ }
+
+ // Process embedded textures
+ if (image.mBufferView >= 0)
+ {
+ return extractTextureToTempFile(texture_index, texture_type);
}
- LL_INFOS("GLTF_DEBUG") << "Processing model " << base_name << LL_ENDL;
+ return "";
+}
+
+bool LLGLTFLoader::validateTextureIndex(S32 texture_index, S32& source_index)
+{
+ if (texture_index < 0 || texture_index >= mGLTFAsset.mTextures.size())
+ return false;
+
+ source_index = mGLTFAsset.mTextures[texture_index].mSource;
+ if (source_index < 0 || source_index >= mGLTFAsset.mImages.size())
+ return false;
+
+ return true;
+}
- if (instance_count > 0)
+std::string LLGLTFLoader::generateMaterialName(S32 material_index, S32 fallback_index)
+{
+ if (material_index >= 0 && material_index < mGLTFAsset.mMaterials.size())
{
- pModel->mLabel = base_name + "_copy_" + std::to_string(instance_count);
+ LL::GLTF::Material* material = &mGLTFAsset.mMaterials[material_index];
+ std::string materialName = material->mName;
+
+ if (materialName.empty())
+ {
+ materialName = "mat" + std::to_string(material_index);
+ }
+ return materialName;
}
else
{
- pModel->mLabel = base_name;
+ return fallback_index >= 0 ? "mat_default" + std::to_string(fallback_index) : "mat_default";
}
+}
+
+bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const std::string& base_name, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats)
+{
+ // Set the requested label for the floater display and uploading
+ pModel->mRequestedLabel = gDirUtilp->getBaseFileName(mFilename, true);
+ // Set only name, suffix will be added later
+ pModel->mLabel = base_name;
+
+ LL_DEBUGS("GLTF_DEBUG") << "Processing model " << pModel->mLabel << LL_ENDL;
pModel->ClearFacesAndMaterials();
@@ -550,11 +739,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skinIdx];
size_t jointCnt = gltf_skin.mJoints.size();
- gltf_joint_index_use.resize(jointCnt);
+ gltf_joint_index_use.resize(jointCnt, 0);
for (size_t i = 0; i < jointCnt; ++i)
{
- if (mJointNames[i].empty())
+ if (mJointNames[skinIdx][i].empty())
{
// This might need to hold a substitute index
gltf_joint_index_use[i] = -1; // mark as unsupported
@@ -572,91 +761,11 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
LLVolumeFace face;
std::vector<GLTFVertex> vertices;
- LLImportMaterial impMat;
- impMat.mDiffuseColor = LLColor4::white; // Default color
-
- // Process material if available
- if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size())
- {
- LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial];
-
- // Set diffuse color from base color factor
- impMat.mDiffuseColor = LLColor4(
- material->mPbrMetallicRoughness.mBaseColorFactor[0],
- material->mPbrMetallicRoughness.mBaseColorFactor[1],
- material->mPbrMetallicRoughness.mBaseColorFactor[2],
- material->mPbrMetallicRoughness.mBaseColorFactor[3]
- );
-
- // Process base color texture if it exists
- if (material->mPbrMetallicRoughness.mBaseColorTexture.mIndex >= 0)
- {
- S32 texIndex = material->mPbrMetallicRoughness.mBaseColorTexture.mIndex;
- if (texIndex < mGLTFAsset.mTextures.size())
- {
- S32 sourceIndex = mGLTFAsset.mTextures[texIndex].mSource;
- if (sourceIndex >= 0 && sourceIndex < mGLTFAsset.mImages.size())
- {
- LL::GLTF::Image& image = mGLTFAsset.mImages[sourceIndex];
-
- // Use URI as texture file name
- if (!image.mUri.empty())
- {
- // URI might be a remote URL or a local path
- std::string filename = image.mUri;
-
- // Extract just the filename from the URI
- size_t pos = filename.find_last_of("/\\");
- if (pos != std::string::npos)
- {
- filename = filename.substr(pos + 1);
- }
-
- // Store the texture filename
- impMat.mDiffuseMapFilename = filename;
- impMat.mDiffuseMapLabel = material->mName.empty() ? filename : material->mName;
-
- LL_INFOS("GLTF_IMPORT") << "Found texture: " << impMat.mDiffuseMapFilename
- << " for material: " << material->mName << LL_ENDL;
-
- LLSD args;
- args["Message"] = "TextureFound";
- args["TEXTURE_NAME"] = impMat.mDiffuseMapFilename;
- args["MATERIAL_NAME"] = material->mName;
- mWarningsArray.append(args);
-
- // If the image has a texture loaded already, use it
- if (image.mTexture.notNull())
- {
- impMat.setDiffuseMap(image.mTexture->getID());
- LL_INFOS("GLTF_IMPORT") << "Using existing texture ID: " << image.mTexture->getID().asString() << LL_ENDL;
- }
- else
- {
- // Texture will be loaded later through the callback system
- LL_INFOS("GLTF_IMPORT") << "Texture needs loading: " << impMat.mDiffuseMapFilename << LL_ENDL;
- }
- }
- else if (image.mTexture.notNull())
- {
- // No URI but we have a texture, use it directly
- impMat.setDiffuseMap(image.mTexture->getID());
- LL_INFOS("GLTF_IMPORT") << "Using existing texture ID without URI: " << image.mTexture->getID().asString() << LL_ENDL;
- }
- else if (image.mBufferView >= 0)
- {
- // For embedded textures (no URI but has buffer data)
- std::string temp_filename = extractTextureToTempFile(texIndex, "base_color");
- if (!temp_filename.empty())
- {
- impMat.mDiffuseMapFilename = temp_filename;
- impMat.mDiffuseMapLabel = material->mName.empty() ? temp_filename : material->mName;
- }
- }
- }
- }
- }
- }
+ // Use cached material processing
+ LLGLTFImportMaterial cachedMat = processMaterial(prim.mMaterial, pModel->getNumVolumeFaces() - 1);
+ LLImportMaterial impMat = cachedMat;
+ std::string materialName = cachedMat.name;
+ mats[materialName] = impMat;
if (prim.getIndexCount() % 3 != 0)
{
@@ -839,26 +948,8 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
}
}
- // Create a unique material name for this primitive
- std::string materialName;
- if (prim.mMaterial >= 0 && prim.mMaterial < mGLTFAsset.mMaterials.size())
- {
- LL::GLTF::Material* material = &mGLTFAsset.mMaterials[prim.mMaterial];
- materialName = material->mName;
-
- if (materialName.empty())
- {
- materialName = "mat" + std::to_string(prim.mMaterial);
- }
- }
- else
- {
- materialName = "mat_default" + std::to_string(pModel->getNumVolumeFaces() - 1);
- }
- mats[materialName] = impMat;
-
// Indices handling
- if (faceVertices.size() >= USHRT_MAX)
+ if (faceVertices.size() >= VERTICIES_LIMIT)
{
// Will have to remap 32 bit indices into 16 bit indices
// For the sake of simplicity build vector of 32 bit indices first
@@ -881,22 +972,40 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
}
}
- // remap 32 bit into multiple 16 bit ones
+ // Generates a vertex remap table with no gaps in the resulting sequence
+ std::vector<U32> remap(faceVertices.size());
+ size_t vertex_count = meshopt_generateVertexRemap(&remap[0], &indices_32[0], indices_32.size(), &faceVertices[0], faceVertices.size(), sizeof(LLVolumeFace::VertexData));
+
+ // Manually remap vertices
+ std::vector<LLVolumeFace::VertexData> optimized_vertices(vertex_count);
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ optimized_vertices[i] = faceVertices[remap[i]];
+ }
+
+ std::vector<U32> optimized_indices(indices_32.size());
+ meshopt_remapIndexBuffer(&optimized_indices[0], &indices_32[0], indices_32.size(), &remap[0]);
+
+ // Sort indices to improve mesh splits (reducing amount of duplicated indices)
+ meshopt_optimizeVertexCache(&optimized_indices[0], &optimized_indices[0], indices_32.size(), vertex_count);
+
std::vector<U16> indices_16;
- std::vector<S64> vertices_remap; // should it be a point map?
- vertices_remap.resize(faceVertices.size(), -1);
+ std::vector<S64> vertices_remap;
+ vertices_remap.resize(vertex_count, -1);
+ S32 created_faces = 0;
std::vector<LLVolumeFace::VertexData> face_verts;
min = glm::vec3(FLT_MAX);
max = glm::vec3(-FLT_MAX);
- for (size_t idx = 0; idx < indices_32.size(); idx++)
+
+ for (size_t idx = 0; idx < optimized_indices.size(); idx++)
{
- size_t vert_index = indices_32[idx];
+ size_t vert_index = optimized_indices[idx];
if (vertices_remap[vert_index] == -1)
{
// First encounter, add it
size_t new_vert_idx = face_verts.size();
vertices_remap[vert_index] = (S64)new_vert_idx;
- face_verts.push_back(faceVertices[vert_index]);
+ face_verts.push_back(optimized_vertices[vert_index]);
vert_index = new_vert_idx;
// Update min/max bounds
@@ -925,7 +1034,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
}
indices_16.push_back((U16)vert_index);
- if (indices_16.size() % 3 == 0 && face_verts.size() >= 65532)
+ if (indices_16.size() % 3 == 0 && face_verts.size() >= VERTICIES_LIMIT - 1)
{
LLVolumeFace face;
face.fillFromLegacyData(face_verts, indices_16);
@@ -933,6 +1042,7 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0);
pModel->getVolumeFaces().push_back(face);
pModel->getMaterialList().push_back(materialName);
+ created_faces++;
std::fill(vertices_remap.begin(), vertices_remap.end(), -1);
indices_16.clear();
@@ -950,7 +1060,17 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
face.mExtents[1] = LLVector4a(max.x, max.y, max.z, 0);
pModel->getVolumeFaces().push_back(face);
pModel->getMaterialList().push_back(materialName);
+ created_faces++;
}
+
+ LL_INFOS("GLTF_IMPORT") << "Primitive " << (S32)prim_idx << " from model " << pModel->mLabel
+ << " is over vertices limit, it was split into " << created_faces
+ << " faces" << LL_ENDL;
+ LLSD args;
+ args["Message"] = "ModelSplitPrimitive";
+ args["MODEL_NAME"] = pModel->mLabel;
+ args["FACE_COUNT"] = created_faces;
+ mWarningsArray.append(args);
}
else
{
@@ -996,11 +1116,18 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
S32 replacement_index = 0;
std::vector<S32> gltfindex_to_joitindex_map;
size_t jointCnt = gltf_skin.mJoints.size();
- gltfindex_to_joitindex_map.resize(jointCnt);
+ 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<std::string, S32> goup_use_count;
+
+ for (const auto& elem : mJointGroups)
+ {
+ goup_use_count[elem.second.mGroup] = 0;
+ goup_use_count[elem.second.mParentGroup] = 0;
+ }
+
// Assume that 'Torso' group is always in use since that's what everything else is attached to
goup_use_count["Torso"] = 1;
// Note that Collisions and Extra groups are all over the place, might want to include them from the start
@@ -1053,7 +1180,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;
}
@@ -1088,16 +1215,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"] = "ModelTooManyJoint";
+ 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);
}
@@ -1135,6 +1264,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
mInverseBindMatrices.resize(skin_idx + 1);
mAlternateBindMatrices.resize(skin_idx + 1);
mJointNames.resize(skin_idx + 1);
+ mJointUsage.resize(skin_idx + 1);
mValidJointsCount.resize(skin_idx + 1, 0);
}
@@ -1144,7 +1274,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
for (S32 i = 0; i < joint_count; i++)
{
S32 joint = skin.mJoints[i];
- LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint];
+ const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint];
JointNodeData& data = joints_data[joint];
data.mNodeIdx = joint;
data.mJointListIdx = i;
@@ -1173,21 +1303,8 @@ void LLGLTFLoader::populateJointsFromSkin(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
+ // This is needed because gltf skeleton doesn't necessarily match viewer's skeleton.
glm::mat4 ident(1.0);
for (auto &viewer_data : mViewerJointData)
{
@@ -1197,8 +1314,10 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
for (S32 i = 0; i < joint_count; i++)
{
S32 joint = skin.mJoints[i];
- LL::GLTF::Node jointNode = mGLTFAsset.mNodes[joint];
+ const LL::GLTF::Node &jointNode = mGLTFAsset.mNodes[joint];
std::string legal_name(jointNode.mName);
+
+ // Viewer supports a limited set of joints, mark them as legal
bool legal_joint = false;
if (mJointMap.find(legal_name) != mJointMap.end())
{
@@ -1210,6 +1329,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
{
mJointNames[skin_idx].emplace_back();
}
+ mJointUsage[skin_idx].push_back(0);
// Compute bind matrices
@@ -1223,8 +1343,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
}
else if (inverse_count > i)
{
- // Transalte existing bind matrix to viewer's skeleton
- // todo: probably should be 'to viewer's overriden skeleton'
+ // Transalte existing bind matrix to viewer's overriden skeleton
glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]);
glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix;
glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name);
@@ -1232,20 +1351,20 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original);
LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix));
- LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL;
+ LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL;
mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform));
}
else
{
// If bind matrices aren't present (they are optional in gltf),
// assume an identy matrix
- // todo: find a model with this, might need to use rotated matrix
+ // todo: find a model with this, might need to use YZ rotated matrix
glm::mat4 inv_bind(1.0f);
glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name);
inv_bind = glm::inverse(skeleton_transform * inv_bind);
LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(inv_bind));
- LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL;
+ LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Generated val: " << gltf_transform << LL_ENDL;
mInverseBindMatrices[skin_idx].push_back(LLMatrix4a(gltf_transform));
}
@@ -1257,7 +1376,7 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
LLMatrix4 newInverse = LLMatrix4(mInverseBindMatrices[skin_idx].back().getF32ptr());
newInverse.setTranslation(original_joint_transform.getTranslation());
- LL_INFOS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL;
+ LL_DEBUGS("GLTF_DEBUG") << "mAlternateBindMatrix name: " << legal_name << " val: " << newInverse << LL_ENDL;
mAlternateBindMatrices[skin_idx].push_back(LLMatrix4a(newInverse));
if (legal_joint)
@@ -1268,6 +1387,21 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx)
mJointsFromNode.push_front(legal_name);
}
}
+
+ S32 valid_joints = mValidJointsCount[skin_idx];
+ if (valid_joints < joint_count)
+ {
+ LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx
+ << " defines " << joint_count
+ << " joints, but only " << valid_joints
+ << " were recognized and are compatible." << LL_ENDL;
+ LLSD args;
+ args["Message"] = "SkinUsupportedJoints";
+ args["SKIN_INDEX"] = skin_idx;
+ args["JOINT_COUNT"] = joint_count;
+ args["LEGAL_COUNT"] = valid_joints;
+ mWarningsArray.append(args);
+ }
}
void LLGLTFLoader::populateJointGroups()
@@ -1279,109 +1413,6 @@ void LLGLTFLoader::populateJointGroups()
}
}
-
-S32 LLGLTFLoader::findClosestValidJoint(S32 source_joint, const LL::GLTF::Skin& gltf_skin) const
-{
- S32 source_joint_node = gltf_skin.mJoints[source_joint];
- S32 root_node = source_joint_node;
- S32 found_node = source_joint_node;
- S32 size = (S32)gltf_skin.mJoints.size();
- do
- {
- root_node = found_node;
- for (S32 i = 0; i < size; i++)
- {
- S32 joint = gltf_skin.mJoints[i];
- const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
- std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node);
- if (it != jointNode.mChildren.end())
- {
- // Found node's parent
- found_node = joint;
- if (mJointMap.find(jointNode.mName) != mJointMap.end())
- {
- return i;
- }
- break;
- }
- }
- } while (root_node != found_node);
-
- return -1;
-}
-
-S32 LLGLTFLoader::findValidRootJointNode(S32 source_joint_node, const LL::GLTF::Skin& gltf_skin) const
-{
- S32 root_node = 0;
- S32 found_node = source_joint_node;
- S32 size = (S32)gltf_skin.mJoints.size();
- do
- {
- root_node = found_node;
- for (S32 i = 0; i < size; i++)
- {
- S32 joint = gltf_skin.mJoints[i];
- const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
-
- if (mJointMap.find(jointNode.mName) != mJointMap.end())
- {
- std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node);
- if (it != jointNode.mChildren.end())
- {
- // Found node's parent
- found_node = joint;
- break;
- }
- }
- }
- } while (root_node != found_node);
-
- return root_node;
-}
-
-S32 LLGLTFLoader::findGLTFRootJointNode(const LL::GLTF::Skin& gltf_skin) const
-{
- S32 root_node = 0;
- S32 found_node = 0;
- S32 size = (S32)gltf_skin.mJoints.size();
- do
- {
- root_node = found_node;
- for (S32 i = 0; i < size; i++)
- {
- S32 joint = gltf_skin.mJoints[i];
- const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[joint];
- std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), root_node);
- if (it != jointNode.mChildren.end())
- {
- // Found node's parent
- found_node = joint;
- break;
- }
- }
- } while (root_node != found_node);
-
- LL_INFOS("GLTF_DEBUG") << "mJointList name: ";
- const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[root_node];
- LL_CONT << jointNode.mName << " index: " << root_node << LL_ENDL;
- return root_node;
-}
-
-S32 LLGLTFLoader::findParentNode(S32 node) const
-{
- S32 size = (S32)mGLTFAsset.mNodes.size();
- for (S32 i = 0; i < size; i++)
- {
- const LL::GLTF::Node& jointNode = mGLTFAsset.mNodes[i];
- std::vector<S32>::const_iterator it = std::find(jointNode.mChildren.begin(), jointNode.mChildren.end(), node);
- if (it != jointNode.mChildren.end())
- {
- return i;
- }
- }
- return -1;
-}
-
void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &parent_group)
{
JointGroups& jount_group_data = mJointGroups[viewer_data.mName];
@@ -1396,7 +1427,6 @@ void LLGLTFLoader::buildJointGroup(LLJointData& viewer_data, const std::string &
void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& parent_support_rest) const
{
- glm::mat4 new_lefover(1.f);
glm::mat4 rest(1.f);
joints_name_to_node_map_t::iterator found_node = names_to_nodes.find(viewer_data.mName);
if (found_node != names_to_nodes.end())
@@ -1434,13 +1464,30 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map
glm::quat rotation;
glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective);
+ // Viewer allows overrides, which are base joint with applied translation override.
+ // fortunately normal bones use only translation, without rotation or scale
node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity<glm::quat>(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1));
- glm::mat4 override_joint = node.mOverrideMatrix;
- override_joint = glm::scale(override_joint, viewer_data.mScale);
+ glm::mat4 overriden_joint = node.mOverrideMatrix;
- rest = parent_rest * override_joint;
- node.mOverrideRestMatrix = rest;
+ // todo: if gltf bone had rotation or scale, they probably should be saved here
+ // then applied to bind matrix
+ rest = parent_rest * overriden_joint;
+ if (viewer_data.mIsJoint)
+ {
+ node.mOverrideRestMatrix = rest;
+ }
+ else
+ {
+ // This is likely incomplete or even wrong.
+ // Viewer Collision bones specify rotation and scale.
+ // Importer should apply rotation and scale to this matrix and save as needed
+ // then subsctruct them from bind matrix
+ // Todo: get models that use collision bones, made by different programs
+
+ overriden_joint = glm::scale(overriden_joint, viewer_data.mScale);
+ node.mOverrideRestMatrix = parent_support_rest * overriden_joint;
+ }
}
else
{
@@ -1574,7 +1621,7 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin)
for (S32 i= 0; i < size; i++)
{
S32 joint = gltf_skin.mJoints[i];
- auto joint_node = mGLTFAsset.mNodes[joint];
+ const LL::GLTF::Node &joint_node = mGLTFAsset.mNodes[joint];
// todo: we are doing this search thing everywhere,
// just pre-translate every joint
@@ -1604,6 +1651,47 @@ void LLGLTFLoader::checkForXYrotation(const LL::GLTF::Skin& gltf_skin)
}
}
+void LLGLTFLoader::checkGlobalJointUsage()
+{
+ // Check if some joints remained unused
+ for (S32 skin_idx = 0; skin_idx < (S32)mGLTFAsset.mSkins.size(); ++skin_idx)
+ {
+ const LL::GLTF::Skin& gltf_skin = mGLTFAsset.mSkins[skin_idx];
+ S32 joint_count = (S32)gltf_skin.mJoints.size();
+ S32 used_joints = 0;
+ for (S32 i = 0; i < joint_count; ++i)
+ {
+ S32 joint = gltf_skin.mJoints[i];
+ if (mJointUsage[skin_idx][i] == 0)
+ {
+ // Joint is unused, log it
+ LL_INFOS("GLTF_DEBUG") << "Joint " << mJointNames[skin_idx][i]
+ << " in skin " << skin_idx << " is unused." << LL_ENDL;
+ }
+ else
+ {
+ used_joints++;
+ }
+ }
+
+ S32 valid_joints = mValidJointsCount[skin_idx];
+ if (valid_joints > used_joints)
+ {
+ S32 unsed_joints = valid_joints - used_joints;
+ LL_INFOS("GLTF_IMPORT") << "Skin " << skin_idx
+ << " declares " << valid_joints
+ << " valid joints, of them " << unsed_joints
+ << " remained unused" << LL_ENDL;
+ LLSD args;
+ args["Message"] = "SkinUnusedJoints";
+ args["SKIN_INDEX"] = (S32)skin_idx;
+ args["JOINT_COUNT"] = valid_joints;
+ args["USED_COUNT"] = used_joints;
+ mWarningsArray.append(args);
+ }
+ }
+}
+
std::string LLGLTFLoader::extractTextureToTempFile(S32 textureIndex, const std::string& texture_type)
{
if (textureIndex < 0 || textureIndex >= mGLTFAsset.mTextures.size())
@@ -1705,6 +1793,27 @@ void LLGLTFLoader::notifyUnsupportedExtension(bool unsupported)
}
args["EXT"] = ext;
mWarningsArray.append(args);
+
+ LL_WARNS("GLTF_IMPORT") << "Model uses unsupported extension: " << ext << LL_ENDL;
+ }
+}
+
+size_t LLGLTFLoader::getSuffixPosition(const std::string &label)
+{
+ if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1))
+ {
+ return label.rfind('_');
+ }
+ return -1;
+}
+
+std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Mesh& mesh)
+{
+ size_t ext_pos = getSuffixPosition(mesh.mName);
+ if (ext_pos != -1)
+ {
+ return mesh.mName.substr(0, ext_pos);
}
+ return mesh.mName;
}