Skip to content
Merged
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
212 changes: 63 additions & 149 deletions indra/newview/gltf/llgltfloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,100 +147,14 @@ bool LLGLTFLoader::parseMeshes()
populateJointFromSkin(skin);
}

/* Two-pass mesh processing approach:
* 1. First pass: Calculate global bounds across all meshes to determine proper scaling
* and centering for the entire model. This ensures consistent normalization.
* 2. Second pass: Process each mesh node with the calculated global scale factor and
* center offset, ensuring the entire model is properly proportioned and centered.
*/

// First pass: Calculate global bounds across all meshes in the model
LLVector3 global_min_bounds(FLT_MAX, FLT_MAX, FLT_MAX);
LLVector3 global_max_bounds(-FLT_MAX, -FLT_MAX, -FLT_MAX);
bool has_geometry = false;

// Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up
LLMatrix4 coord_system_rotation;
coord_system_rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f);

// Gather bounds from all meshes
for (auto &node : mGLTFAsset.mNodes)
for (auto& node : mGLTFAsset.mNodes)
{
auto meshidx = node.mMesh;
if (meshidx >= 0 && meshidx < mGLTFAsset.mMeshes.size())
{
auto mesh = mGLTFAsset.mMeshes[meshidx];

// Make node matrix valid for correct transformation
node.makeMatrixValid();

LLMatrix4 node_matrix(glm::value_ptr(node.mMatrix));
LLMatrix4 node_transform;
node_transform *= coord_system_rotation; // Apply coordinate rotation first
node_transform *= node_matrix;

// Examine all primitives in this mesh
for (auto prim : mesh.mPrimitives)
{
if (prim.getVertexCount() >= USHRT_MAX)
continue;

for (U32 i = 0; i < prim.getVertexCount(); i++)
{
// Transform vertex position by node transform
LLVector4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f);
LLVector4 transformed_pos = pos * node_transform;

global_min_bounds.mV[VX] = llmin(global_min_bounds.mV[VX], transformed_pos.mV[VX]);
global_min_bounds.mV[VY] = llmin(global_min_bounds.mV[VY], transformed_pos.mV[VY]);
global_min_bounds.mV[VZ] = llmin(global_min_bounds.mV[VZ], transformed_pos.mV[VZ]);

global_max_bounds.mV[VX] = llmax(global_max_bounds.mV[VX], transformed_pos.mV[VX]);
global_max_bounds.mV[VY] = llmax(global_max_bounds.mV[VY], transformed_pos.mV[VY]);
global_max_bounds.mV[VZ] = llmax(global_max_bounds.mV[VZ], transformed_pos.mV[VZ]);

has_geometry = true;
}
}
}
}

// Calculate model dimensions and center point for the entire model
F32 global_scale_factor = 1.0f;
LLVector3 global_center_offset(0.0f, 0.0f, 0.0f);

if (has_geometry
&& mJointList.empty()) // temporary disable offset and scaling for rigged meshes
{
// Calculate bounding box center - this will be our new origin
LLVector3 center = (global_min_bounds + global_max_bounds) * 0.5f;
global_center_offset = -center; // Offset to move center to origin

// Calculate dimensions of the bounding box
LLVector3 dimensions = global_max_bounds - global_min_bounds;

// Find the maximum dimension rather than the diagonal
F32 max_dimension = llmax(dimensions.mV[VX], llmax(dimensions.mV[VY], dimensions.mV[VZ]));

// Always scale to the target size to ensure consistent bounding box
const F32 TARGET_SIZE = 1.0f; // Target size for maximum dimension in meters

if (max_dimension > 0.0f)
{
// Calculate scale factor to normalize model's maximum dimension to TARGET_SIZE
global_scale_factor = TARGET_SIZE / max_dimension;

LL_INFOS("GLTF_IMPORT") << "Model dimensions: " << dimensions.mV[VX] << "x"
<< dimensions.mV[VY] << "x" << dimensions.mV[VZ]
<< ", max dimension: " << max_dimension
<< ", applying global scale factor: " << global_scale_factor
<< ", global centering offset: (" << global_center_offset.mV[VX] << ", "
<< global_center_offset.mV[VY] << ", " << global_center_offset.mV[VZ] << ")" << LL_ENDL;
}
// Make node matrix valid for correct transformation
node.makeMatrixValid();
}

// Second pass: Process each node with the global scale and offset
for (auto &node : mGLTFAsset.mNodes)
// Process each node
for (auto& node : mGLTFAsset.mNodes)
{
LLMatrix4 transformation;
material_map mats;
Expand All @@ -252,7 +166,7 @@ bool LLGLTFLoader::parseMeshes()
{
LLModel* pModel = new LLModel(volume_params, 0.f);
auto mesh = mGLTFAsset.mMeshes[meshidx];
if (populateModelFromMesh(pModel, mesh, node, mats, global_scale_factor, global_center_offset) &&
if (populateModelFromMesh(pModel, mesh, node, mats) &&
(LLModel::NO_ERRORS == pModel->getStatus()) &&
validate_model(pModel))
{
Expand Down Expand Up @@ -292,7 +206,7 @@ bool LLGLTFLoader::parseMeshes()
else
{
setLoadState(ERROR_MODEL + pModel->getStatus());
delete (pModel);
delete pModel;
return false;
}
}
Expand Down Expand Up @@ -330,14 +244,27 @@ void LLGLTFLoader::computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S3
combined_transform = node_transform;
}

bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats,
const F32 scale_factor, const LLVector3& center_offset)
bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh& mesh, const LL::GLTF::Node& nodeno, material_map& mats)
{
pModel->mLabel = mesh.mName;
pModel->ClearFacesAndMaterials();

S32 skinIdx = nodeno.mSkin;

// Pre-compute coordinate system rotation matrix (GLTF Y-up to SL Z-up)
static const glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));

// Compute final combined transform matrix (hierarchy + coordinate rotation)
S32 node_index = static_cast<S32>(&nodeno - &mGLTFAsset.mNodes[0]);
glm::mat4 hierarchy_transform;
computeCombinedNodeTransform(mGLTFAsset, node_index, hierarchy_transform);

// Combine transforms: coordinate rotation applied to hierarchy transform
const glm::mat4 final_transform = coord_system_rotation * hierarchy_transform;

// Pre-compute normal transform matrix (transpose of inverse of upper-left 3x3)
const glm::mat3 normal_transform = glm::transpose(glm::inverse(glm::mat3(final_transform)));

// Mark unsuported joints with '-1' so that they won't get added into weights
// GLTF maps all joints onto all meshes. Gather use count per mesh to cut unused ones.
std::vector<S32> gltf_joint_index_use_count;
Expand Down Expand Up @@ -373,11 +300,9 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
// So primitives already have all of the data we need for a given face in SL land.
// Primitives may only ever have a single material assigned to them - as the relation is 1:1 in terms of intended draw call
// count. Just go ahead and populate faces direct from the GLTF primitives here. -Geenz 2025-04-07
LLVolumeFace face;
LLVolumeFace::VertexMapData::PointMap point_map;

LLVolumeFace face;
std::vector<GLTFVertex> vertices;
std::vector<U16> indices;
std::vector<U16> indices;

LLImportMaterial impMat;
impMat.mDiffuseColor = LLColor4::white; // Default color
Expand Down Expand Up @@ -461,36 +386,19 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
}
}

// Compute combined transform for this node considering parent hierarchy
S32 node_index = static_cast<S32>(&nodeno - &mGLTFAsset.mNodes[0]);
glm::mat4 combined_transform;
computeCombinedNodeTransform(mGLTFAsset, node_index, combined_transform);

// Create coordinate system rotation matrix - GLTF is Y-up, SL is Z-up
glm::mat4 coord_system_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));

// Apply coordinate system rotation to the combined transform
combined_transform = coord_system_rotation * combined_transform;

// Apply the global scale and center offset to all vertices
for (U32 i = 0; i < prim.getVertexCount(); i++)
{
// Transform vertex position with combined hierarchy transform (including coord rotation)
// Use pre-computed final_transform
glm::vec4 pos(prim.mPositions[i][0], prim.mPositions[i][1], prim.mPositions[i][2], 1.0f);
glm::vec4 transformed_pos = combined_transform * pos;
glm::vec4 transformed_pos = final_transform * pos;

// Apply scaling and centering after hierarchy transform
GLTFVertex vert;
vert.position = glm::vec3(
(transformed_pos.x + center_offset.mV[VX]) * scale_factor,
(transformed_pos.y + center_offset.mV[VY]) * scale_factor,
(transformed_pos.z + center_offset.mV[VZ]) * scale_factor
);
vert.position = glm::vec3(transformed_pos);

// Also rotate the normal vector
glm::vec4 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2], 0.0f);
glm::vec4 transformed_normal = coord_system_rotation * normal_vec;
vert.normal = glm::normalize(glm::vec3(transformed_normal));
// Use pre-computed normal_transform
glm::vec3 normal_vec(prim.mNormals[i][0], prim.mNormals[i][1], prim.mNormals[i][2]);
vert.normal = glm::normalize(normal_transform * normal_vec);

vert.uv0 = glm::vec2(prim.mTexCoords0[i][0], -prim.mTexCoords0[i][1]);

Expand Down Expand Up @@ -526,25 +434,36 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
indices.push_back(prim.mIndexArray[i]);
}

// Check for empty vertex array before processing
if (vertices.empty())
{
LL_WARNS("GLTF_IMPORT") << "Empty vertex array for primitive" << LL_ENDL;
continue; // Skip this primitive
}

std::vector<LLVolumeFace::VertexData> faceVertices;
glm::vec3 min = glm::vec3(0);
glm::vec3 max = glm::vec3(0);
glm::vec3 min = glm::vec3(FLT_MAX);
glm::vec3 max = glm::vec3(-FLT_MAX);

for (U32 i = 0; i < vertices.size(); i++)
{
LLVolumeFace::VertexData vert;
if (i == 0 || vertices[i].position.x > max.x)
max.x = vertices[i].position.x;
if (i == 0 || vertices[i].position.y > max.y)
max.y = vertices[i].position.y;
if (i == 0 || vertices[i].position.z > max.z)
max.z = vertices[i].position.z;
if (i == 0 || vertices[i].position.x < min.x)
min.x = vertices[i].position.x;
if (i == 0 || vertices[i].position.y < min.y)
min.y = vertices[i].position.y;
if (i == 0 || vertices[i].position.z < min.z)
min.z = vertices[i].position.z;

// Update min/max bounds
if (i == 0)
{
min = max = vertices[i].position;
}
else
{
min.x = std::min(min.x, vertices[i].position.x);
min.y = std::min(min.y, vertices[i].position.y);
min.z = std::min(min.z, vertices[i].position.z);
max.x = std::max(max.x, vertices[i].position.x);
max.y = std::max(max.y, vertices[i].position.y);
max.z = std::max(max.z, vertices[i].position.z);
}

LLVector4a position = LLVector4a(vertices[i].position.x, vertices[i].position.y, vertices[i].position.z);
LLVector4a normal = LLVector4a(vertices[i].normal.x, vertices[i].normal.y, vertices[i].normal.z);
vert.setPosition(position);
Expand Down Expand Up @@ -591,22 +510,22 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&
std::vector<LLModel::JointWeight> wght;
F32 total = 0.f;

for (U32 i = 0; i < llmin((U32)4, (U32)weight_list.size()); ++i)
for (U32 j = 0; j < llmin((U32)4, (U32)weight_list.size()); ++j)
{
// take up to 4 most significant weights
// Ported from the DAE loader - however, GLTF right now only supports up to four weights per vertex.
wght.push_back(weight_list[i]);
total += weight_list[i].mWeight;
wght.push_back(weight_list[j]);
total += weight_list[j].mWeight;
}

if (total != 0.f)
{
F32 scale = 1.f / total;
if (scale != 1.f)
{ // normalize weights
for (U32 i = 0; i < wght.size(); ++i)
for (U32 j = 0; j < wght.size(); ++j)
{
wght[i].mWeight *= scale;
wght[j].mWeight *= scale;
}
}
}
Expand Down Expand Up @@ -701,19 +620,14 @@ bool LLGLTFLoader::populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh&

if (i < gltf_skin.mInverseBindMatricesData.size())
{
// Process bind matrix
// Use pre-computed coord_system_rotation instead of recreating it
LL::GLTF::mat4 gltf_mat = gltf_skin.mInverseBindMatricesData[i];

// For inverse bind matrices, we need to:
// 1. Get the original bind matrix by inverting
// 2. Apply coordinate rotation to the original
// 3. Invert again to get the rotated inverse bind matrix
glm::mat4 original_bind_matrix = glm::inverse(gltf_mat);
glm::mat4 coord_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4 rotated_original = coord_rotation * original_bind_matrix;
glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix;
glm::mat4 rotated_inverse_bind_matrix = glm::inverse(rotated_original);

LLMatrix4 gltf_transform(glm::value_ptr(rotated_inverse_bind_matrix));
LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(rotated_inverse_bind_matrix));
skin_info.mInvBindMatrix.push_back(LLMatrix4a(gltf_transform));

LL_INFOS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " val: " << gltf_transform << LL_ENDL;
Expand Down
3 changes: 1 addition & 2 deletions indra/newview/gltf/llgltfloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ class LLGLTFLoader : public LLModelLoader
bool parseMaterials();
void uploadMaterials();
void computeCombinedNodeTransform(const LL::GLTF::Asset& asset, S32 node_index, glm::mat4& combined_transform);
bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats,
const F32 scale_factor = 1.0f, const LLVector3& center_offset = LLVector3());
bool populateModelFromMesh(LLModel* pModel, const LL::GLTF::Mesh &mesh, const LL::GLTF::Node &node, material_map& mats);
void populateJointFromSkin(const LL::GLTF::Skin& skin);
LLUUID imageBufferToTextureUUID(const gltf_texture& tex);

Expand Down