diff --git a/dUgcServer/UgcBricks.cpp b/dUgcServer/Bricks/UgcBricks.cpp similarity index 100% rename from dUgcServer/UgcBricks.cpp rename to dUgcServer/Bricks/UgcBricks.cpp diff --git a/dUgcServer/UgcBricks.h b/dUgcServer/Bricks/UgcBricks.h similarity index 100% rename from dUgcServer/UgcBricks.h rename to dUgcServer/Bricks/UgcBricks.h diff --git a/dUgcServer/UgcCdClient.cpp b/dUgcServer/Bricks/UgcCdClient.cpp similarity index 100% rename from dUgcServer/UgcCdClient.cpp rename to dUgcServer/Bricks/UgcCdClient.cpp diff --git a/dUgcServer/UgcCdClient.h b/dUgcServer/Bricks/UgcCdClient.h similarity index 100% rename from dUgcServer/UgcCdClient.h rename to dUgcServer/Bricks/UgcCdClient.h diff --git a/dUgcServer/UgcPalette.cpp b/dUgcServer/Bricks/UgcPalette.cpp similarity index 100% rename from dUgcServer/UgcPalette.cpp rename to dUgcServer/Bricks/UgcPalette.cpp diff --git a/dUgcServer/UgcPalette.h b/dUgcServer/Bricks/UgcPalette.h similarity index 100% rename from dUgcServer/UgcPalette.h rename to dUgcServer/Bricks/UgcPalette.h diff --git a/dUgcServer/CMakeLists.txt b/dUgcServer/CMakeLists.txt index a9b20a349..0f80c9dce 100644 --- a/dUgcServer/CMakeLists.txt +++ b/dUgcServer/CMakeLists.txt @@ -1,25 +1,34 @@ +# The UGC server (docs/UgcServer.md), in folders by role: +# Bricks/ the client's brick data: primitives, Materials.xml, LU Toolbox's palette, the CDClient's module data +# Model/ player models built from LXFML, car and rocket module combinations, glitter +# Render/ the software rasterizer, ambient occlusion, hidden-face removal, icon parameters and pose +# Formats/ the files written for the client and the dashboard (NIF, DDS, PNG, sd0) +# Processing/ the job queue and workers, the CPU budget, stored files +# UgcServer.cpp the server: settings, the master link, HTTP routes + # The pure part of the UGC server (brick data, meshes, rendering, file formats, storage), shared with the tests set(DUGC_SOURCES - "UgcBricks.cpp" - "UgcFormats.cpp" - "UgcGlitter.cpp" - "UgcHsr.cpp" - "UgcIconParams.cpp" - "UgcIconPose.cpp" - "UgcJobs.cpp" - "UgcModel.cpp" - "UgcModular.cpp" - "UgcPalette.cpp" - "UgcRender.cpp" - "UgcStorage.cpp" - "UgcThrottle.cpp" + "Bricks/UgcBricks.cpp" + "Bricks/UgcPalette.cpp" + "Formats/UgcFormats.cpp" + "Model/UgcGlitter.cpp" + "Model/UgcModel.cpp" + "Model/UgcModular.cpp" + "Processing/UgcJobs.cpp" + "Processing/UgcStorage.cpp" + "Processing/UgcThrottle.cpp" + "Render/UgcHsr.cpp" + "Render/UgcIconParams.cpp" + "Render/UgcIconPose.cpp" + "Render/UgcRender.cpp" ) add_library(dUgc STATIC ${DUGC_SOURCES}) -target_include_directories(dUgc PUBLIC "." "${PROJECT_SOURCE_DIR}/thirdparty/MD5" "${PROJECT_SOURCE_DIR}/thirdparty/nlohmann") +target_include_directories(dUgc PUBLIC "." "Bricks" "Formats" "Model" "Processing" "Render" + "${PROJECT_SOURCE_DIR}/thirdparty/MD5" "${PROJECT_SOURCE_DIR}/thirdparty/nlohmann") target_link_libraries(dUgc PUBLIC dCommon glm::glm tinyxml2 MD5) -add_executable(UgcServer "UgcServer.cpp" "UgcProcessor.cpp" "UgcCdClient.cpp") +add_executable(UgcServer "UgcServer.cpp" "Processing/UgcProcessor.cpp" "Bricks/UgcCdClient.cpp") target_include_directories(UgcServer PRIVATE "${PROJECT_SOURCE_DIR}/dCommon" @@ -42,5 +51,5 @@ target_link_libraries(UgcServer ${COMMON_LIBRARIES} dWeb dServer dUgc) # Rendering is far too slow unoptimized (minutes a model instead of seconds), so it is optimized in every build type if(NOT MSVC) - set_source_files_properties("UgcRender.cpp" "UgcHsr.cpp" "UgcModel.cpp" "UgcPalette.cpp" "UgcGlitter.cpp" PROPERTIES COMPILE_OPTIONS "-O2") + set_source_files_properties("Render/UgcRender.cpp" "Render/UgcHsr.cpp" "Model/UgcModel.cpp" "Bricks/UgcPalette.cpp" "Model/UgcGlitter.cpp" PROPERTIES COMPILE_OPTIONS "-O2") endif() diff --git a/dUgcServer/UgcFormats.cpp b/dUgcServer/Formats/UgcFormats.cpp similarity index 97% rename from dUgcServer/UgcFormats.cpp rename to dUgcServer/Formats/UgcFormats.cpp index 64b43f275..5114772cc 100644 --- a/dUgcServer/UgcFormats.cpp +++ b/dUgcServer/Formats/UgcFormats.cpp @@ -228,8 +228,8 @@ namespace { m_VertexColor = nif.Add("NiVertexColorProperty", std::move(vertexColor.Data())); } - // A white NiMaterialProperty with this emissive color (grey) - static std::string Material(float emissive) { + // A white NiMaterialProperty with this emissive color (grey) and alpha + static std::string Material(float emissive, float alpha = 1.0f) { Writer material; WriteNet(material, -1); for (int i = 0; i < 3; i++) material.Float(1.0f); // ambient @@ -237,7 +237,7 @@ namespace { for (int i = 0; i < 3; i++) material.Float(0.0f); // specular for (int i = 0; i < 3; i++) material.Float(emissive); material.Float(4.0f); // glossiness, as the game's brick models - material.Float(1.0f); // alpha + material.Float(alpha); return std::move(material.Data()); } @@ -383,8 +383,15 @@ namespace { specular.U16(0); // off m_Specular = m_Nif.Add("NiSpecularProperty", std::move(specular.Data())); } - (void)transparent; + // The client draws a shape in its sorted, blended pass only when the material's alpha is under 0.99999 + // (ShaderCommon::GetAlphaFlags 0x0109f5a0; the NiAlphaProperty's blend flag isn't read): at 1.0 a + // transparent brick is drawn solid, with blending off. The game's own brick models give their S01_Alpha + // shapes 0.9999, and so do we (made once a file, only when there is a transparent shape). int32_t material = m_Material; + if (transparent) { + if (m_MaterialAlpha < 0) m_MaterialAlpha = m_Nif.Add("NiMaterialProperty", Material(0.0f, 0.9999f)); + material = m_MaterialAlpha; + } if (emissive > 0.0f) { auto [it, added] = m_Emissive.try_emplace(emissive, -1); if (added) it->second = m_Nif.Add("NiMaterialProperty", Material(emissive)); @@ -409,6 +416,7 @@ namespace { private: NifBuilder& m_Nif; int32_t m_Material{ -1 }; + int32_t m_MaterialAlpha{ -1 }; // transparent shapes' (alpha 0.9999) int32_t m_VertexColor{ -1 }; int32_t m_Alpha{ -1 }; int32_t m_Specular{ -1 }; diff --git a/dUgcServer/UgcFormats.h b/dUgcServer/Formats/UgcFormats.h similarity index 100% rename from dUgcServer/UgcFormats.h rename to dUgcServer/Formats/UgcFormats.h diff --git a/dUgcServer/UgcGlitter.cpp b/dUgcServer/Model/UgcGlitter.cpp similarity index 100% rename from dUgcServer/UgcGlitter.cpp rename to dUgcServer/Model/UgcGlitter.cpp diff --git a/dUgcServer/UgcGlitter.h b/dUgcServer/Model/UgcGlitter.h similarity index 100% rename from dUgcServer/UgcGlitter.h rename to dUgcServer/Model/UgcGlitter.h diff --git a/dUgcServer/UgcModel.cpp b/dUgcServer/Model/UgcModel.cpp similarity index 100% rename from dUgcServer/UgcModel.cpp rename to dUgcServer/Model/UgcModel.cpp diff --git a/dUgcServer/UgcModel.h b/dUgcServer/Model/UgcModel.h similarity index 100% rename from dUgcServer/UgcModel.h rename to dUgcServer/Model/UgcModel.h diff --git a/dUgcServer/UgcModular.cpp b/dUgcServer/Model/UgcModular.cpp similarity index 100% rename from dUgcServer/UgcModular.cpp rename to dUgcServer/Model/UgcModular.cpp diff --git a/dUgcServer/UgcModular.h b/dUgcServer/Model/UgcModular.h similarity index 100% rename from dUgcServer/UgcModular.h rename to dUgcServer/Model/UgcModular.h diff --git a/dUgcServer/UgcJobs.cpp b/dUgcServer/Processing/UgcJobs.cpp similarity index 100% rename from dUgcServer/UgcJobs.cpp rename to dUgcServer/Processing/UgcJobs.cpp diff --git a/dUgcServer/UgcJobs.h b/dUgcServer/Processing/UgcJobs.h similarity index 100% rename from dUgcServer/UgcJobs.h rename to dUgcServer/Processing/UgcJobs.h diff --git a/dUgcServer/UgcProcessor.cpp b/dUgcServer/Processing/UgcProcessor.cpp similarity index 100% rename from dUgcServer/UgcProcessor.cpp rename to dUgcServer/Processing/UgcProcessor.cpp diff --git a/dUgcServer/UgcProcessor.h b/dUgcServer/Processing/UgcProcessor.h similarity index 100% rename from dUgcServer/UgcProcessor.h rename to dUgcServer/Processing/UgcProcessor.h diff --git a/dUgcServer/UgcStorage.cpp b/dUgcServer/Processing/UgcStorage.cpp similarity index 100% rename from dUgcServer/UgcStorage.cpp rename to dUgcServer/Processing/UgcStorage.cpp diff --git a/dUgcServer/UgcStorage.h b/dUgcServer/Processing/UgcStorage.h similarity index 100% rename from dUgcServer/UgcStorage.h rename to dUgcServer/Processing/UgcStorage.h diff --git a/dUgcServer/UgcThrottle.cpp b/dUgcServer/Processing/UgcThrottle.cpp similarity index 100% rename from dUgcServer/UgcThrottle.cpp rename to dUgcServer/Processing/UgcThrottle.cpp diff --git a/dUgcServer/UgcThrottle.h b/dUgcServer/Processing/UgcThrottle.h similarity index 100% rename from dUgcServer/UgcThrottle.h rename to dUgcServer/Processing/UgcThrottle.h diff --git a/dUgcServer/UgcHsr.cpp b/dUgcServer/Render/UgcHsr.cpp similarity index 100% rename from dUgcServer/UgcHsr.cpp rename to dUgcServer/Render/UgcHsr.cpp diff --git a/dUgcServer/UgcHsr.h b/dUgcServer/Render/UgcHsr.h similarity index 100% rename from dUgcServer/UgcHsr.h rename to dUgcServer/Render/UgcHsr.h diff --git a/dUgcServer/UgcIconParams.cpp b/dUgcServer/Render/UgcIconParams.cpp similarity index 100% rename from dUgcServer/UgcIconParams.cpp rename to dUgcServer/Render/UgcIconParams.cpp diff --git a/dUgcServer/UgcIconParams.h b/dUgcServer/Render/UgcIconParams.h similarity index 100% rename from dUgcServer/UgcIconParams.h rename to dUgcServer/Render/UgcIconParams.h diff --git a/dUgcServer/UgcIconPose.cpp b/dUgcServer/Render/UgcIconPose.cpp similarity index 100% rename from dUgcServer/UgcIconPose.cpp rename to dUgcServer/Render/UgcIconPose.cpp diff --git a/dUgcServer/UgcIconPose.h b/dUgcServer/Render/UgcIconPose.h similarity index 100% rename from dUgcServer/UgcIconPose.h rename to dUgcServer/Render/UgcIconPose.h diff --git a/dUgcServer/UgcRender.cpp b/dUgcServer/Render/UgcRender.cpp similarity index 100% rename from dUgcServer/UgcRender.cpp rename to dUgcServer/Render/UgcRender.cpp diff --git a/dUgcServer/UgcRender.h b/dUgcServer/Render/UgcRender.h similarity index 100% rename from dUgcServer/UgcRender.h rename to dUgcServer/Render/UgcRender.h diff --git a/tests/dUgcTests/UgcTests.cpp b/tests/dUgcTests/UgcTests.cpp index 9ad6f2e0d..979bcc866 100644 --- a/tests/dUgcTests/UgcTests.cpp +++ b/tests/dUgcTests/UgcTests.cpp @@ -1097,8 +1097,8 @@ TEST(UgcShaders, OffIsByteIdenticalToBefore) { // The floating point results (color variation, occlusion) are the same on one platform and compiler; the hashes // were taken with GCC on x86-64 Linux #if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__) - EXPECT_EQ(UgcFormats::Md5Hex(nif), "b0fcb707d36ccdb62e951bf50593e633"); - EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(outcome.files.at("model.noao.nif.gz"))), "db55bd2c8567a862b2c96942aa5a2617"); + EXPECT_EQ(UgcFormats::Md5Hex(nif), "7a7176731afd837da83450d260ed6832"); + EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(outcome.files.at("model.noao.nif.gz"))), "19d8a2f748015e1f9d680a60fb7565d5"); EXPECT_EQ(UgcFormats::Md5Hex(outcome.files.at("icon.png")), "032ff7df236a636a4c609071d9b46181"); #endif } @@ -1624,10 +1624,10 @@ TEST(UgcHsr, OffIsByteIdenticalToBefore) { const auto other = UgcJobs::ProcessModel(LXFML5, library, settings, 99); ASSERT_TRUE(other.ok) << other.error; #if defined(__linux__) && defined(__x86_64__) && defined(__GNUC__) && !defined(__clang__) - EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(outcome.files.at("model.nif.gz"))), "f91b88e46a92e9472710854902b25c9a"); - EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(outcome.files.at("model.noao.nif.gz"))), "58a779933695da04bf9b3459c8369528"); + EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(outcome.files.at("model.nif.gz"))), "a8f6b7022a7c087d1659d67a16e9791f"); + EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(outcome.files.at("model.noao.nif.gz"))), "2da0810cbd2470732e4732a8128da2ab"); EXPECT_EQ(UgcFormats::Md5Hex(outcome.files.at("icon.png")), "032ff7df236a636a4c609071d9b46181"); - EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(other.files.at("model.nif.gz"))), "7eddc020a3e4bf22ed1df0183b042ced"); + EXPECT_EQ(UgcFormats::Md5Hex(*ZCompression::Gunzip(other.files.at("model.nif.gz"))), "289088a76248d35203cd961f2772ae21"); #endif } @@ -1764,3 +1764,31 @@ TEST(UgcThrottle, CancelStopsJobsAtTheirNextCheckpoint) { UgcThrottle::Cancel(false); EXPECT_NO_THROW(UgcThrottle::Checkpoint()); } + +// Transparent shapes get a material alpha of 0.9999 as the game's own brick models' S01_Alpha shapes: the client only +// blends a shape whose material alpha is under 0.99999 (ShaderCommon::GetAlphaFlags); opaque shapes keep 1.0 +TEST(UgcFormats, TransparentShapesHaveTheGamesAlphaMaterial) { + const auto mesh = Quad({ 0.2f, 0.4f, 0.8f, 0.6f }); + const auto alphaOf = [](const std::string& nif) { + std::vector alphas; + // Every NiMaterialProperty block ends with glossiness 4.0 and the alpha: find glossiness then read the alpha + for (size_t i = 0; i + 8 <= nif.size(); i++) { + float gloss = 0.0f; + std::memcpy(&gloss, nif.data() + i, 4); + if (gloss != 4.0f) continue; + float alpha = 0.0f; + std::memcpy(&alpha, nif.data() + i + 4, 4); + if (alpha > 0.9f && alpha <= 1.0f) alphas.push_back(alpha); + } + return alphas; + }; + const auto mixed = UgcFormats::WriteLodNif("SceneNode_Model", { + { "S01_Opaque_Model", false, { { 0.0f, 100.0f, "LOD_0", { &mesh } } } }, + { "S01_Alpha_Model", true, { { 0.0f, 100.0f, "LOD_0", { &mesh } } } } }); + const auto alphas = alphaOf(mixed); + EXPECT_NE(std::find(alphas.begin(), alphas.end(), 1.0f), alphas.end()); + EXPECT_NE(std::find(alphas.begin(), alphas.end(), 0.9999f), alphas.end()); + const auto opaque = UgcFormats::WriteLodNif("SceneNode_Model", { { "S01_Opaque_Model", false, { { 0.0f, 100.0f, "LOD_0", { &mesh } } } } }); + const auto opaqueAlphas = alphaOf(opaque); + EXPECT_EQ(std::find(opaqueAlphas.begin(), opaqueAlphas.end(), 0.9999f), opaqueAlphas.end()); +}