diff --git a/indra/llmath/almeshsimplifier.cpp b/indra/llmath/almeshsimplifier.cpp index 8a6b5451a3..57c0a7dd33 100644 --- a/indra/llmath/almeshsimplifier.cpp +++ b/indra/llmath/almeshsimplifier.cpp @@ -450,6 +450,103 @@ namespace return options; } + // mesh's face simplified on its own, as ALMeshSimplifier::simplifyFace + // makes it: its triangles, numbered as mesh's vertices are, in + // simplified. Returns their index count, 0 where no triangle is left. + size_t simplifyOwnFace(const Mesh& mesh, S32 face, const ALMeshSimplifier::Target& goal, ALMeshSimplifier::Seams seams, + std::vector& simplified) + { + using Seams = ALMeshSimplifier::Seams; + const U32 first = mesh.first[face]; + const U32 vertex_count = mesh.first[face + 1] - first; + const size_t index_count = mesh.first_index[face + 1] - mesh.first_index[face]; + simplified.clear(); + if (index_count < 3) + { + return 0; + } + + // The face's own triangles, numbered from its first vertex. + std::vector indices(mesh.indices.begin() + mesh.first_index[face], mesh.indices.begin() + mesh.first_index[face + 1]); + for (U32& index : indices) + { + index -= first; + } + + // Error is the model's, relative to its size; the simplifier takes it + // relative to the face's. + const F32 face_extent = meshopt_simplifyScale(mesh.positions[first].getF32ptr(), vertex_count, sizeof(LLVector4a)); + const F32 to_face = face_extent > 0.f ? mesh.extent / face_extent : 1.f; + const F32 error = goal.error * to_face; + + const size_t target_count = targetCount(index_count, goal.decimator); + simplified.resize(index_count); + F32 result_error = 0.f; + size_t count = 0; + if (seams == Seams::SLOPPY) + { + count = meshopt_simplifySloppy(simplified.data(), indices.data(), index_count, mesh.positions[first].getF32ptr(), vertex_count, + sizeof(LLVector4a), nullptr, target_count, llmin(error, 1.f), &result_error); + } + else + { + // A face that meets another keeps its vertices where it meets it, + // which the other's simplification keeps as well, and all of its + // parts, as a part of one face may be joined to another face's. A + // face meets another along a border, one's or the other's; its + // borders that meet no face are free, and so is its inside where + // another face lies over it. + const bool meets = std::any_of(mesh.shared.begin() + first, mesh.shared.begin() + first + vertex_count, + [](U8 shared) { return shared != 0; }); + std::vector locks; + if (seams != Seams::ALL) + { + protect(mesh, seams, first, vertex_count, locks); + } + if (meets) + { + locks.resize(vertex_count, 0); + for (U32 v = 0; v < vertex_count; ++v) + { + if (mesh.held[first + v]) + { + locks[v] |= (U8)meshopt_SimplifyVertex_Lock; + } + } + } + count = simplifyMesh(mesh, first, vertex_count, indices.data(), index_count, target_count, error, seamOptions(seams, goal), locks, + goal.distant && !meets, simplified.data(), result_error); + } + if (count < 3) + { + simplified.clear(); + return 0; + } + + simplified.resize(count); + for (U32& index : simplified) + { + index += first; + } + return count; + } + + // The vertices the triangles name. + size_t namedVertexCount(const std::vector& indices, size_t vertex_count) + { + std::vector named(vertex_count, 0); + size_t count = 0; + for (U32 index : indices) + { + if (!named[index]) + { + named[index] = 1; + ++count; + } + } + return count; + } + // What a simplified face takes from the face it is made from besides // its vertices and triangles. void copyLayout(const LLVolumeFace& layout, LLVolumeFace& face) @@ -641,33 +738,29 @@ F32 ALMeshSimplifier::simplifyModel(LLVolume& target, const Target& goal, Seams to.insert(to.end(), simplified.begin() + i, simplified.begin() + i + 3); } - for (S32 f = 0; f < faces; ++f) + // Nothing is written to target until every face is known to fit. + if (std::any_of(face_indices.begin(), face_indices.end(), + [&](const std::vector& indices) { return namedVertexCount(indices, mesh.vertexCount()) > U16_MAX; })) { - if (face_indices[f].size() < 3) + LL_WARNS("MeshSimplifier") << "A face of a simplified model takes more vertices than 16-bit indices reach; " + << "simplifying each face on its own" << LL_ENDL; + count = 0; + for (S32 f = 0; f < faces; ++f) { - continue; + count += simplifyOwnFace(mesh, f, goal, seams, face_indices[f]); } - if (!compact(base, f, face_indices[f].data(), face_indices[f].size(), target.getVolumeFace(f))) + if (count < 3) { - LL_WARNS("MeshSimplifier") << "A face of a simplified model takes more vertices than 16-bit indices reach; " - << "simplifying each face on its own" << LL_ENDL; - size_t face_count = 0; - S32 kept = 0; - for (S32 g = 0; g < faces; ++g) - { - if (simplifyFace(g, target.getVolumeFace(g), goal, seams) > 0.f) - { - face_count += target.getVolumeFace(g).mNumIndices; - ++kept; - } - } - return kept ? (F32)index_count / (F32)face_count : -1.f; + return -1.f; } } + for (S32 f = 0; f < faces; ++f) { - if (face_indices[f].size() < 3) + if (face_indices[f].size() < 3 || !compact(base, f, face_indices[f].data(), face_indices[f].size(), target.getVolumeFace(f))) { + // Every face fits, as counted above. + llassert(face_indices[f].size() < 3); makeEmpty(base.getVolumeFace(f), target.getVolumeFace(f)); } } @@ -676,84 +769,15 @@ F32 ALMeshSimplifier::simplifyModel(LLVolume& target, const Target& goal, Seams F32 ALMeshSimplifier::simplifyFace(S32 face, LLVolumeFace& target, const Target& goal, Seams seams) const { - const LLVolume& base = mBase; const Mesh& mesh = *mMesh; - const U32 first = mesh.first[face]; - const U32 vertex_count = mesh.first[face + 1] - first; - const size_t index_count = mesh.first_index[face + 1] - mesh.first_index[face]; - if (index_count < 3) - { - makeEmpty(base.getVolumeFace(face), target); - return -1.f; - } - - // The face's own triangles, numbered from its first vertex. - std::vector indices(mesh.indices.begin() + mesh.first_index[face], mesh.indices.begin() + mesh.first_index[face + 1]); - for (U32& index : indices) - { - index -= first; - } - - // Error is the model's, relative to its size; the simplifier takes it - // relative to the face's. - const F32 face_extent = meshopt_simplifyScale(mesh.positions[first].getF32ptr(), vertex_count, sizeof(LLVector4a)); - const F32 to_face = face_extent > 0.f ? mesh.extent / face_extent : 1.f; - const F32 error = goal.error * to_face; - - const size_t target_count = targetCount(index_count, goal.decimator); - std::vector simplified(index_count); - F32 result_error = 0.f; - size_t count = 0; - if (seams == Seams::SLOPPY) - { - count = meshopt_simplifySloppy(simplified.data(), indices.data(), index_count, mesh.positions[first].getF32ptr(), vertex_count, - sizeof(LLVector4a), nullptr, target_count, llmin(error, 1.f), &result_error); - } - else - { - // A face that meets another keeps its vertices where it meets it, - // which the other's simplification keeps as well, and all of its - // parts, as a part of one face may be joined to another face's. A - // face meets another along a border, one's or the other's; its - // borders that meet no face are free, and so is its inside where - // another face lies over it. - const bool meets = std::any_of(mesh.shared.begin() + first, mesh.shared.begin() + first + vertex_count, - [](U8 shared) { return shared != 0; }); - std::vector locks; - if (seams != Seams::ALL) - { - protect(mesh, seams, first, vertex_count, locks); - } - if (meets) - { - locks.resize(vertex_count, 0); - for (U32 v = 0; v < vertex_count; ++v) - { - if (mesh.held[first + v]) - { - locks[v] |= (U8)meshopt_SimplifyVertex_Lock; - } - } - } - count = simplifyMesh(mesh, first, vertex_count, indices.data(), index_count, target_count, error, seamOptions(seams, goal), locks, - goal.distant && !meets, simplified.data(), result_error); - } - if (count < 3) - { - makeEmpty(base.getVolumeFace(face), target); - return -1.f; - } - - for (size_t i = 0; i < count; ++i) - { - simplified[i] += first; - } - if (!compact(base, face, simplified.data(), count, target)) + std::vector simplified; + const size_t count = simplifyOwnFace(mesh, face, goal, seams, simplified); + if (count < 3 || !compact(mBase, face, simplified.data(), count, target)) { // A face's own vertices are never more than 16-bit indices reach. - llassert(false); - makeEmpty(base.getVolumeFace(face), target); + llassert(count < 3); + makeEmpty(mBase.getVolumeFace(face), target); return -1.f; } - return (F32)index_count / (F32)count; + return (F32)(mesh.first_index[face + 1] - mesh.first_index[face]) / (F32)count; } diff --git a/indra/llmath/almeshsimplifier.h b/indra/llmath/almeshsimplifier.h index 59fe36181b..cb5ab7ee97 100644 --- a/indra/llmath/almeshsimplifier.h +++ b/indra/llmath/almeshsimplifier.h @@ -90,7 +90,8 @@ class ALMeshSimplifier // or -1 where no triangle was left, in which case target's faces are as // they were. A face left with no triangle is one of three indices to a // vertex at the origin. Where a face would take more vertices than 16-bit - // indices reach, each face is simplified on its own instead. + // indices reach, each face is simplified on its own instead, as + // simplifyFace simplifies it. F32 simplifyModel(LLVolume& target, const Target& goal, Seams seams) const; // Simplifies base's face on its own into target. A face that shares a diff --git a/indra/llmath/tests/almeshsimplifier_test.cpp b/indra/llmath/tests/almeshsimplifier_test.cpp index 1594fed00e..e8b8770067 100644 --- a/indra/llmath/tests/almeshsimplifier_test.cpp +++ b/indra/llmath/tests/almeshsimplifier_test.cpp @@ -1091,6 +1091,105 @@ namespace CHECK(differ == 0); } + // A flat sheet of 600 columns by 200 rows of points as two faces of + // 60,000 vertices: the first has every other column's points, two + // columns a quad, and the second every point of the first 300 columns, + // a column a quad. A sloppy simplification that keeps every triangle + // takes the first face's vertex where the second's lies on it, so that + // the second's triangles with their first corner there are the first + // face's, and name half of the second's vertices besides its own. + LLPointer sheetOverColumns() + { + constexpr S32 COLUMNS = 600; + constexpr S32 ROWS = 200; + auto point = [](S32 column, S32 row, Geometry& g) + { + const F32 x = (F32)column / (COLUMNS - 1); + const F32 y = (F32)row / (ROWS - 1); + g.add(LLVector3(x - 0.5f, y - 0.5f, 0.f), LLVector3(0.f, 0.f, 1.f), LLVector2(x, y)); + }; + auto grid = [](S32 columns, Geometry& g) + { + for (S32 j = 0; j + 1 < ROWS; ++j) + { + for (S32 i = 0; i + 1 < columns; ++i) + { + const U16 c0 = (U16)(i + j * columns); + const U16 c1 = (U16)(c0 + 1); + const U16 c2 = (U16)(c0 + columns); + const U16 c3 = (U16)(c2 + 1); + g.triangle(c0, c1, c3); + g.triangle(c0, c3, c2); + } + } + }; + Geometry first, second; + for (S32 row = 0; row < ROWS; ++row) + { + for (S32 column = 0; column < COLUMNS; column += 2) + { + point(column, row, first); + } + for (S32 column = 0; column < COLUMNS / 2; ++column) + { + point(column, row, second); + } + } + grid(COLUMNS / 2, first); + grid(COLUMNS / 2, second); + return volumeOf({ first, second }); + } + + TEST_CASE("almeshsimplifier: simplified together, a face past 16-bit indices makes each face simplify on its own", "[almeshsimplifier]") + { + ArenaInstalled installed; + LLPointer base = sheetOverColumns(); + const ALMeshSimplifier simplifier(*base); + const ALMeshSimplifier::Target goal = toCount(1.f); + + // The scene: the sheet simplified as one mesh, as simplifyModel + // simplifies it, leaves the first face's triangles naming more + // vertices than 16-bit indices reach. + const LLVolumeFace& first = base->getVolumeFace(0); + const LLVolumeFace& second = base->getVolumeFace(1); + std::vector positions(first.mPositions, first.mPositions + first.mNumVertices); + positions.insert(positions.end(), second.mPositions, second.mPositions + second.mNumVertices); + std::vector indices(first.mIndices, first.mIndices + first.mNumIndices); + for (S32 i = 0; i < second.mNumIndices; ++i) + { + indices.push_back(second.mIndices[i] + (U32)first.mNumVertices); + } + std::vector together(indices.size()); + const size_t together_count = meshopt_simplifySloppy(together.data(), indices.data(), indices.size(), positions[0].getF32ptr(), + positions.size(), sizeof(LLVector4a), nullptr, indices.size(), 1.f, nullptr); + std::set named; + for (size_t i = 0; i < together_count; i += 3) + { + if (together[i] < (U32)first.mNumVertices) + { + named.insert(together.begin() + i, together.begin() + i + 3); + } + } + INFO(fmt::format("simplified as one mesh, the first face's triangles name {} vertices", named.size())); + REQUIRE(named.size() > U16_MAX); + + LLPointer target = targetFor(*base); + REQUIRE(simplifier.simplifyModel(*target, goal, Seams::SLOPPY) > 0.f); + for (S32 f = 0; f < 2; ++f) + { + INFO(fmt::format("face {}", f)); + LLVolumeFace alone; + REQUIRE(simplifier.simplifyFace(f, alone, goal, Seams::SLOPPY) > 0.f); + const LLVolumeFace& face = target->getVolumeFace(f); + REQUIRE(wellFormed(face)); + CHECK(fromBase(face, *base)); + REQUIRE(face.mNumVertices == alone.mNumVertices); + REQUIRE(face.mNumIndices == alone.mNumIndices); + CHECK(std::equal(face.mIndices, face.mIndices + face.mNumIndices, alone.mIndices)); + CHECK(memcmp(face.mPositions, alone.mPositions, sizeof(LLVector4a) * face.mNumVertices) == 0); + } + } + // ------------------------------------------------------------------ // Normals diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index d0e8c8810f..9c4e52a0d4 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -36,12 +36,64 @@ using namespace LL::GLTF; -// A primitive unrolled into a triangle list, for its tangents and flat normals -struct MikktMesh +// get the vertex index of each corner of a primitive's triangles, as a triangle list +// returns false if the Primitive cannot be converted to a triangle list +static bool get_triangle_corners(const Primitive* prim, std::vector& corners) +{ + bool indexed = !prim->mIndexArray.empty(); + size_t count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); + llassert(count <= size_t(U32_MAX)); + + corners.clear(); + + if (prim->mMode == Primitive::Mode::TRIANGLES) + { + for (U32 i = 0; i < U32(count / 3 * 3); ++i) + { + corners.push_back(i); + } + } + else if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP || + prim->mMode == Primitive::Mode::TRIANGLE_FAN) + { + for (U32 tri_idx = 0; size_t(tri_idx) + 2 < count; ++tri_idx) + { + U32 idx[3] = { tri_idx, tri_idx + 1, tri_idx + 2 }; + + if (prim->mMode == Primitive::Mode::TRIANGLE_FAN) + { + idx[0] = 0; + } + else if (tri_idx % 2 != 0) + { + std::swap(idx[1], idx[2]); + } + + corners.insert(corners.end(), idx, idx + 3); + } + } + else + { + LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32)prim->mMode << LL_ENDL; + return false; + } + + if (indexed) + { + for (U32& corner : corners) + { + corner = prim->mIndexArray[corner]; + } + } + + return true; +} + +// A primitive unrolled into a triangle list, for its flat normals +struct TriangleList { std::vector p; //positions std::vector n; //normals - std::vector t; //tangents std::vector tc0; //texcoords 0 std::vector tc1; //texcoords 1 std::vector c; //colors @@ -52,28 +104,13 @@ struct MikktMesh // returns false if the Primitive cannot be converted to a triangle list bool copy(const Primitive* prim) { - bool indexed = !prim->mIndexArray.empty(); - size_t vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); - - size_t triangle_count = 0; - - if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP || - prim->mMode == Primitive::Mode::TRIANGLE_FAN) + std::vector corners; + if (!get_triangle_corners(prim, corners)) { - triangle_count = vert_count - 2; - } - else if (prim->mMode == Primitive::Mode::TRIANGLES) - { - triangle_count = vert_count / 3; - } - else - { - LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32)prim->mMode << LL_ENDL; return false; } - vert_count = triangle_count * 3; - llassert(vert_count <= size_t(U32_MAX)); // triangle_count will also naturally be under the limit + size_t vert_count = corners.size(); p.resize(vert_count); n.resize(vert_count); @@ -81,15 +118,6 @@ struct MikktMesh c.resize(vert_count); bool has_normals = !prim->mNormals.empty(); - if (has_normals) - { - n.resize(vert_count); - } - bool has_tangents = !prim->mTangents.empty(); - if (has_tangents) - { - t.resize(vert_count); - } bool rigged = !prim->mWeights.empty(); if (rigged) @@ -104,63 +132,27 @@ struct MikktMesh tc1.resize(vert_count); } - for (U32 tri_idx = 0; tri_idx < U32(triangle_count); ++tri_idx) + for (size_t i = 0; i < vert_count; ++i) { - U32 idx[3] = {0, 0, 0}; + U32 idx = corners[i]; + p[i].set(prim->mPositions[idx].getF32ptr()); + tc0[i].set(prim->mTexCoords0[idx]); + c[i] = prim->mColors[idx]; - if (prim->mMode == Primitive::Mode::TRIANGLES) + if (multi_uv) { - idx[0] = tri_idx * 3; - idx[1] = tri_idx * 3 + 1; - idx[2] = tri_idx * 3 + 2; - } - else if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP) - { - idx[0] = tri_idx; - idx[1] = tri_idx + 1; - idx[2] = tri_idx + 2; - - if (tri_idx % 2 != 0) - { - std::swap(idx[1], idx[2]); - } - } - else if (prim->mMode == Primitive::Mode::TRIANGLE_FAN) - { - idx[0] = 0; - idx[1] = tri_idx + 1; - idx[2] = tri_idx + 2; + tc1[i].set(prim->mTexCoords1[idx]); } - if (indexed) + if (has_normals) { - idx[0] = prim->mIndexArray[idx[0]]; - idx[1] = prim->mIndexArray[idx[1]]; - idx[2] = prim->mIndexArray[idx[2]]; + n[i].set(prim->mNormals[idx].getF32ptr()); } - for (U32 v = 0; v < 3; ++v) + if (rigged) { - U32 i = tri_idx * 3 + v; - p[i].set(prim->mPositions[idx[v]].getF32ptr()); - tc0[i].set(prim->mTexCoords0[idx[v]]); - c[i] = prim->mColors[idx[v]]; - - if (multi_uv) - { - tc1[i].set(prim->mTexCoords1[idx[v]]); - } - - if (has_normals) - { - n[i].set(prim->mNormals[idx[v]].getF32ptr()); - } - - if (rigged) - { - w[i].set(prim->mWeights[idx[v]].getF32ptr()); - j[i] = prim->mJoints[idx[v]]; - } + w[i].set(prim->mWeights[idx].getF32ptr()); + j[i] = prim->mJoints[idx]; } } @@ -185,25 +177,6 @@ struct MikktMesh } } - // MikkTSpace's tangents, as meshoptimizer's compatible mode computes - // them. GLTF's V runs down the texture, the other way from the V a - // tangent's handedness is taken against, so the tangents are those of - // the UVs as they are with w the other way. - void genTangents() - { - t.resize(p.size()); - if (p.empty()) - { - return; - } - meshopt_generateTangents(t[0].mV, nullptr, p.size(), p[0].mV, p.size(), sizeof(LLVector3), n[0].mV, sizeof(LLVector3), tc0[0].mV, - sizeof(LLVector2), meshopt_TangentCompatible); - for (LLVector4& tangent : t) - { - tangent.mV[3] = -tangent.mV[3]; - } - } - // write to target primitive as an indexed triangle list // Only modifies runtime data, does not modify the original GLTF data void write(Primitive* prim) const @@ -213,7 +186,6 @@ struct MikktMesh { { &p[0], sizeof(LLVector3), sizeof(LLVector3) }, { &n[0], sizeof(LLVector3), sizeof(LLVector3) }, - { &t[0], sizeof(LLVector4), sizeof(LLVector4) }, { &tc0[0], sizeof(LLVector2), sizeof(LLVector2) }, { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) } }; @@ -238,7 +210,6 @@ struct MikktMesh prim->mTexCoords0.resize(vert_count); prim->mNormals.resize(vert_count); - prim->mTangents.resize(vert_count); prim->mPositions.resize(vert_count); prim->mColors.resize(vert_count); if (!w.empty()) @@ -263,7 +234,6 @@ struct MikktMesh prim->mPositions[dst_idx].load3(p[src_idx].mV); prim->mNormals[dst_idx].load3(n[src_idx].mV); prim->mTexCoords0[dst_idx] = tc0[src_idx]; - prim->mTangents[dst_idx].loadua(t[src_idx].mV); prim->mColors[dst_idx] = c[src_idx]; if (!w.empty()) @@ -282,6 +252,70 @@ struct MikktMesh } }; +template +static void remap_vertices(std::vector& vertices, const std::vector& remap, size_t vert_count) +{ + std::vector remapped(vert_count); + meshopt_remapVertexBuffer(remapped.data(), vertices.data(), remap.size(), sizeof(T), remap.data()); + vertices.swap(remapped); +} + +// weld a primitive in place into an indexed triangle list of the vertices its triangles use +// Only modifies runtime data, does not modify the original GLTF data +static bool weld(Primitive* prim) +{ + std::vector corners; + if (!get_triangle_corners(prim, corners)) + { + return false; + } + + std::vector mos = + { + { prim->mPositions.data(), sizeof(LLVector3), sizeof(LLVector4a) }, + { prim->mNormals.data(), sizeof(LLVector3), sizeof(LLVector4a) }, + { prim->mTexCoords0.data(), sizeof(LLVector2), sizeof(LLVector2) }, + { prim->mColors.data(), sizeof(LLColor4U), sizeof(LLColor4U) } + }; + + bool rigged = !prim->mWeights.empty(); + if (rigged) + { + mos.push_back({ prim->mWeights.data(), sizeof(LLVector4a), sizeof(LLVector4a) }); + mos.push_back({ prim->mJoints.data(), sizeof(U64), sizeof(U64) }); + } + + bool multi_uv = !prim->mTexCoords1.empty(); + if (multi_uv) + { + mos.push_back({ prim->mTexCoords1.data(), sizeof(LLVector2), sizeof(LLVector2) }); + } + + // only the vertices the corners name are read, and those no corner names are dropped + std::vector remap(prim->mPositions.size()); + size_t vert_count = meshopt_generateVertexRemapMulti(remap.data(), corners.data(), corners.size(), remap.size(), mos.data(), mos.size()); + + prim->mIndexArray.resize(corners.size()); + meshopt_remapIndexBuffer(prim->mIndexArray.data(), corners.data(), corners.size(), remap.data()); + + remap_vertices(prim->mPositions, remap, vert_count); + remap_vertices(prim->mNormals, remap, vert_count); + remap_vertices(prim->mTexCoords0, remap, vert_count); + remap_vertices(prim->mColors, remap, vert_count); + if (rigged) + { + remap_vertices(prim->mWeights, remap, vert_count); + remap_vertices(prim->mJoints, remap, vert_count); + } + if (multi_uv) + { + remap_vertices(prim->mTexCoords1, remap, vert_count); + } + + prim->mGLMode = LLRender::TRIANGLES; + return true; +} + static void vertical_flip(std::vector& texcoords) { @@ -415,56 +449,27 @@ bool Primitive::prep(Asset& asset) if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP) { //no normals and no surfaces, this primitive is unlit - mTangents.clear(); unlit = true; } else { // unroll into non-indexed array of flat shaded triangles - MikktMesh data; + TriangleList data; if (!data.copy(this)) { return false; } data.genNormals(); - data.genTangents(); data.write(this); } } - - if (mTangents.empty() && !unlit) - { // NOTE: must be done last because tangent generation rewrites the other arrays - if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP) - { - // for points and lines, just make sure tangent is perpendicular to normal - mTangents.resize(mNormals.size()); - LLVector4a up(0.f, 0.f, 1.f, 0.f); - LLVector4a left(1.f, 0.f, 0.f, 0.f); - for (U32 i = 0; i < mNormals.size(); ++i) - { - if (fabsf(mNormals[i].getF32ptr()[2]) < 0.999f) - { - mTangents[i] = up.cross3(mNormals[i]); - } - else - { - mTangents[i] = left.cross3(mNormals[i]); - } - - mTangents[i].getF32ptr()[3] = 1.f; - } - } - else + else if (mTangents.empty() && !unlit && + mMode != Mode::POINTS && mMode != Mode::LINES && mMode != Mode::LINE_LOOP && mMode != Mode::LINE_STRIP) + { + if (!weld(this)) { - MikktMesh data; - if (!data.copy(this)) - { - return false; - } - - data.genTangents(); - data.write(this); + return false; } } diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 4155835374..bec580ff53 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1602,23 +1602,28 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d { // Remove progressively more data if we can't reach the target, // keeping the closest a mode came: target_model holds the - // last mode's. + // last mode's, and closest a copy of the faces of the closest + // that fell short, as the passes after it replace them. F32 allowed_ratio_drift = 1.8f; using Seams = ALMeshSimplifier::Seams; F32 precise_ratio = -1.f; - Seams precise_seams = Seams::ALL; + std::vector closest; for (Seams seams : { Seams::ALL, Seams::UV, Seams::NONE }) { const F32 ratio = simplify_model(indices_decimator, seams); - if (ratio > precise_ratio) + const bool closer = ratio > precise_ratio; + if (closer) { precise_ratio = ratio; - precise_seams = seams; } if (ratio >= 0 && ratio * allowed_ratio_drift >= indices_decimator) { break; } + if (closer) + { + target_model->copyFacesTo(closest); + } } if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) @@ -1699,8 +1704,12 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d } else { - // Fallback to normal method, in the mode that came closest - precise_ratio = simplify_model(indices_decimator, precise_seams); + // Fallback to normal method: the faces of the mode that came closest + llassert(closest.size() == (size_t)target_model->getNumVolumeFaces()); + for (S32 face_idx = 0; face_idx < (S32)closest.size(); ++face_idx) + { + target_model->getVolumeFace(face_idx) = closest[face_idx]; + } } LL_DEBUGS("Upload") << "Model " << target_model->getName()