diff --git a/dUgcServer/UgcJobs.cpp b/dUgcServer/UgcJobs.cpp index cc2e7b986..902bbeee5 100644 --- a/dUgcServer/UgcJobs.cpp +++ b/dUgcServer/UgcJobs.cpp @@ -154,7 +154,7 @@ namespace UgcJobs { entry["opaqueAfter"] = model.opaque.TriangleCount(); entry["vertices"] = model.opaque.positions.size() + model.transparent.positions.size(); opaquePieces.push_back(UgcModel::Divide(model.opaque)); - transparentPieces.push_back(UgcModel::Divide(model.transparent)); + transparentPieces.push_back(settings.combineTransparent ? UgcModel::Divide(model.transparent) : UgcModel::SplitAt(model.transparent, model.transparentBricks)); entry["shapes"] = opaquePieces.back().size() + transparentPieces.back().size(); lodStats.push_back(entry); } @@ -181,7 +181,7 @@ namespace UgcJobs { AddDownload(outcome.files, "model.nif", nif); AddDownload(outcome.files, "model.lxfml", lxfml); { - const std::vector> opaque{ UgcModel::Divide(preview.opaque) }, transparent{ UgcModel::Divide(preview.transparent) }; + const std::vector> opaque{ UgcModel::Divide(preview.opaque) }, transparent{ transparentPieces[0] }; outcome.files["model.noao.nif"] = UgcFormats::WriteLodNif("SceneNode_Model", groups(1, opaque, transparent)); } diff --git a/dUgcServer/UgcJobs.h b/dUgcServer/UgcJobs.h index d9ceaae9a..3b34ea706 100644 --- a/dUgcServer/UgcJobs.h +++ b/dUgcServer/UgcJobs.h @@ -24,6 +24,7 @@ namespace UgcJobs { std::vector lods{ 0, 2 }; // brickprimitives levels made (LU Toolbox imports LOD 0 and 2; the client has no 3) UgcModel::LodDistances lodDistances; std::string shaderOpaque{ "01" }; // S_Opaque_...; transparent shapes are always S01 + bool combineTransparent{ false }; // one shape for all transparent bricks, else one per brick (Combine Transparent) UgcRender::OptimizeOptions optimize; // hidden surface removal UgcRender::AoOptions ao; // Bake Lighting (AO Only) UgcRender::IconOptions icon; diff --git a/dUgcServer/UgcModel.cpp b/dUgcServer/UgcModel.cpp index 81edd0875..60c1c0705 100644 --- a/dUgcServer/UgcModel.cpp +++ b/dUgcServer/UgcModel.cpp @@ -204,6 +204,7 @@ namespace UgcModel { transparent = transparent && (luToolbox ? UgcPalette::IsTransparent(materialOf(index)) : library.GetMaterial(id).Transparent()); } auto& mesh = transparent ? model.transparent : model.opaque; + if (transparent) model.transparentBricks.push_back(mesh.indices.size()); const glm::mat3 normalMatrix = glm::transpose(glm::inverse(glm::mat3(part.transform))); for (size_t index = 0; index < design->size(); index++) { const auto& geometry = (*design)[index]; @@ -317,6 +318,30 @@ namespace UgcModel { return model; } + std::vector SplitAt(const Mesh& mesh, const std::vector& starts) { + std::vector pieces; + std::unordered_map remap; + for (size_t i = 0; i < starts.size(); i++) { + const size_t first = starts[i], last = std::min(i + 1 < starts.size() ? starts[i + 1] : mesh.indices.size(), mesh.indices.size()); + if (first >= last) continue; + Mesh piece; + remap.clear(); + for (size_t k = first; k < last; k++) { + const auto source = mesh.indices[k]; + auto [it, added] = remap.try_emplace(source, static_cast(piece.positions.size())); + if (added) { + piece.positions.push_back(mesh.positions[source]); + if (source < mesh.normals.size()) piece.normals.push_back(mesh.normals[source]); + if (source < mesh.colors.size()) piece.colors.push_back(mesh.colors[source]); + if (source < mesh.glow.size()) piece.glow.push_back(mesh.glow[source]); + } + piece.indices.push_back(it->second); + } + pieces.push_back(std::move(piece)); + } + return pieces; + } + void KeepTriangles(Mesh& mesh, const std::vector& keep) { Mesh kept; std::vector remap(mesh.positions.size(), UINT32_MAX); diff --git a/dUgcServer/UgcModel.h b/dUgcServer/UgcModel.h index 0375ffbe3..ef17e9837 100644 --- a/dUgcServer/UgcModel.h +++ b/dUgcServer/UgcModel.h @@ -46,6 +46,7 @@ namespace UgcModel { Mesh opaque; Mesh transparent; std::vector missingDesigns; // designs without geometry in the client, skipped + std::vector transparentBricks; // where each transparent brick's triangles start in transparent.indices size_t bricks{}; bool Empty() const { return opaque.Empty() && transparent.Empty(); } @@ -99,6 +100,9 @@ namespace UgcModel { // A client .nif's meshes as one model (vertex colors times material color; transparent when blended) Model FromNif(const NifFile::Model& nif); + // The mesh cut into pieces at these index offsets (each piece's triangles start at one), e.g. one per brick + std::vector SplitAt(const Mesh& mesh, const std::vector& starts); + // Keeps the triangles whose flag is set (and the vertices they use) void KeepTriangles(Mesh& mesh, const std::vector& keep); diff --git a/dUgcServer/UgcServer.cpp b/dUgcServer/UgcServer.cpp index 1e5b5ec16..e9e32f621 100644 --- a/dUgcServer/UgcServer.cpp +++ b/dUgcServer/UgcServer.cpp @@ -80,6 +80,7 @@ namespace { settings.lodDistances.lod3 = Setting("lod_distance_3", 280.0f); settings.lodDistances.cull = Setting("lod_cull", 10000.0f); if (!Game::config->GetValue("shader_opaque").empty()) settings.shaderOpaque = Game::config->GetValue("shader_opaque"); + settings.combineTransparent = Setting("combine_transparent", 0) != 0; settings.optimize.removeHidden = Setting("remove_hidden_faces", 1) != 0; settings.optimize.groundPlane = Setting("hsr_ground_plane", 0) != 0; settings.optimize.resolution = Setting("optimize_resolution", 1024); diff --git a/resources/ugcconfig.ini b/resources/ugcconfig.ini index 9c7ac289f..e8e3b06c7 100644 --- a/resources/ugcconfig.ini +++ b/resources/ugcconfig.ini @@ -59,8 +59,10 @@ lod_distance_2=100 lod_distance_3=280 lod_cull=10000 -# The shapes' shader number: S_Opaque_Model (transparent shapes are S01_Alpha_Model) +# The shapes' shader number: S_Opaque_Model (transparent shapes are S01_Alpha_Model), and whether all +# transparent bricks are one shape (1) or each its own (0, so the client can sort them) shader_opaque=01 +combine_transparent=0 # Remove faces that can't be seen from anywhere (optimize_resolution: detail of the renders that decide it; # hsr_ground_plane: 1 also removes what can only be seen from below the model) diff --git a/tests/dUgcTests/UgcTests.cpp b/tests/dUgcTests/UgcTests.cpp index 8fe716a18..ee927e1da 100644 --- a/tests/dUgcTests/UgcTests.cpp +++ b/tests/dUgcTests/UgcTests.cpp @@ -375,6 +375,7 @@ TEST(UgcModel, ColorsLikeLuToolbox) { plain.colorVariation = 0.0f; const auto flat = UgcModel::Build(parts, library, plain); EXPECT_EQ(flat.transparent.TriangleCount(), 12u); // only the all-transparent brick + EXPECT_EQ(flat.transparentBricks, std::vector{ 0 }); EXPECT_EQ(flat.opaque.TriangleCount(), 60u); EXPECT_NEAR(flat.opaque.colors[0].r * 255.0f, 222.0f, 0.5f); EXPECT_FLOAT_EQ(flat.opaque.colors[0].a, 1.0f); @@ -447,6 +448,22 @@ TEST(UgcModel, DividesAlongTheLongestSide) { EXPECT_EQ(UgcModel::Divide(mesh, 100, 1000).size(), 1u); } +TEST(UgcModel, SplitsTransparentBricksApart) { + // Two boxes in one mesh, one shape each (LU Toolbox leaves transparent bricks uncombined) + UgcBricks::BrickLibrary library(MakeRes(), 0); + std::string error; + const auto model = UgcModel::Build(UgcModel::ParseLxfml(R"( + + + )", error), library); + ASSERT_EQ(model.transparentBricks.size(), 2u); + const auto pieces = UgcModel::SplitAt(model.transparent, model.transparentBricks); + ASSERT_EQ(pieces.size(), 2u); + EXPECT_EQ(pieces[0].positions.size(), 8u); + EXPECT_EQ(pieces[1].TriangleCount(), 12u); + EXPECT_NEAR(pieces[1].positions[0].x, 5.0f, 1e-5f); +} + TEST(UgcFormats, LodNifReadsBack) { UgcModel::Mesh near, far; near.positions = { { 0, 0, 0 }, { 1, 0, 0 }, { 0, 1, 0 }, { 1, 1, 0 } };