Walk the node graph, accumulating each mesh's transformed vertices/indices. `node_transform` is the cumulative parent transform (column-major glm).
| 215 | // Walk the node graph, accumulating each mesh's transformed vertices/indices. |
| 216 | // `node_transform` is the cumulative parent transform (column-major glm). |
| 217 | void appendNode( |
| 218 | const aiScene* scene, const aiNode* node, const glm::mat4& parent_transform, const glm::mat3& extra_rotation, |
| 219 | const std::vector<std::shared_ptr<const Material>>& materials, MeshData& out) { |
| 220 | // assimp row-major aiMatrix4x4 -> glm column-major. |
| 221 | const aiMatrix4x4& m = node->mTransformation; |
| 222 | glm::mat4 local( |
| 223 | m.a1, m.b1, m.c1, m.d1, // col 0 |
| 224 | m.a2, m.b2, m.c2, m.d2, // col 1 |
| 225 | m.a3, m.b3, m.c3, m.d3, // col 2 |
| 226 | m.a4, m.b4, m.c4, m.d4); // col 3 |
| 227 | const glm::mat4 node_transform = parent_transform * local; |
| 228 | const glm::mat3 normal_matrix = glm::mat3(glm::transpose(glm::inverse(node_transform))); |
| 229 | const glm::mat3 linear = glm::mat3(node_transform); |
| 230 | |
| 231 | for (unsigned int i = 0; i < node->mNumMeshes; ++i) { |
| 232 | const aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; |
| 233 | static const std::shared_ptr<const Material> k_fallback = std::make_shared<const Material>(); |
| 234 | const std::shared_ptr<const Material>& material = |
| 235 | mesh->mMaterialIndex < materials.size() ? materials[mesh->mMaterialIndex] : k_fallback; |
| 236 | const auto base_index = static_cast<std::uint32_t>(out.vertices.size()); |
| 237 | |
| 238 | out.vertices.reserve(out.vertices.size() + mesh->mNumVertices); |
| 239 | for (unsigned int v = 0; v < mesh->mNumVertices; ++v) { |
| 240 | Vertex vert; |
| 241 | const aiVector3D& p = mesh->mVertices[v]; |
| 242 | glm::vec3 pos = glm::vec3(node_transform * glm::vec4(p.x, p.y, p.z, 1.0f)); |
| 243 | glm::vec3 nrm(0.0f, 0.0f, 1.0f); |
| 244 | if (mesh->HasNormals()) { |
| 245 | const aiVector3D& n = mesh->mNormals[v]; |
| 246 | nrm = glm::normalize(normal_matrix * glm::vec3(n.x, n.y, n.z)); |
| 247 | } |
| 248 | vert.position = extra_rotation * pos; |
| 249 | vert.normal = glm::normalize(extra_rotation * nrm); |
| 250 | // Vertex color carries ONLY genuine per-vertex colors (glTF COLOR_0); the |
| 251 | // material base color lives in SubMesh::material and is applied by the |
| 252 | // shader. A white default leaves untextured/non-vertex-colored meshes to be |
| 253 | // driven purely by the material factor (no double-application). |
| 254 | vert.color = |
| 255 | mesh->HasVertexColors(0) |
| 256 | ? glm::vec4(mesh->mColors[0][v].r, mesh->mColors[0][v].g, mesh->mColors[0][v].b, mesh->mColors[0][v].a) |
| 257 | : glm::vec4(1.0f); |
| 258 | if (mesh->HasTextureCoords(0)) { |
| 259 | const aiVector3D& uv = mesh->mTextureCoords[0][v]; |
| 260 | vert.uv = glm::vec2(uv.x, uv.y); |
| 261 | } |
| 262 | // Tangent basis for normal mapping (aiProcess_CalcTangentSpace). Transform |
| 263 | // into world like the geometry, re-orthogonalize against the final normal |
| 264 | // (Gram-Schmidt), and store the bitangent handedness in .w. |
| 265 | if (mesh->HasTangentsAndBitangents()) { |
| 266 | const aiVector3D& t = mesh->mTangents[v]; |
| 267 | const aiVector3D& b = mesh->mBitangents[v]; |
| 268 | glm::vec3 tan = extra_rotation * (linear * glm::vec3(t.x, t.y, t.z)); |
| 269 | glm::vec3 bit = extra_rotation * (linear * glm::vec3(b.x, b.y, b.z)); |
| 270 | tan = tan - vert.normal * glm::dot(vert.normal, tan); |
| 271 | if (glm::dot(tan, tan) > 1e-12f) { |
| 272 | tan = glm::normalize(tan); |
| 273 | const float sign = glm::dot(glm::cross(vert.normal, tan), bit) < 0.0f ? -1.0f : 1.0f; |
| 274 | vert.tangent = glm::vec4(tan, sign); |
no test coverage detected