From f3922b3bcbf37b4d010eca0c9297797e468b456b Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 28 Sep 2026 10:18:07 -0500 Subject: [PATCH] feat(ugc): brushed_colors, LEGO color ids drawn as brushed steel The client's Materials.xml has no brushedSteel or matteSteel colors, so the Brushed Steel shader (mapShaders 89) was never used. brushed_colors names color ids to draw with it whatever their type (e.g. the drum lacquered 298, 300, 1002, 1004); a named color wins over the metal and glow colors. Empty by default. The docs note that the client registers the brushed reflection and noise textures itself (0x00453730), so the .nif needs none. Co-Authored-By: Claude Opus 5.5 --- dDashboardServer/routes/SettingsCatalog.cpp | 1 + dUgcServer/UgcModel.cpp | 1 + dUgcServer/UgcModel.h | 4 +++- dUgcServer/UgcServer.cpp | 9 +++++++++ docs/UgcServer.md | 3 ++- resources/ugcconfig.ini | 2 ++ tests/dUgcTests/UgcTests.cpp | 6 ++++++ 7 files changed, 24 insertions(+), 2 deletions(-) diff --git a/dDashboardServer/routes/SettingsCatalog.cpp b/dDashboardServer/routes/SettingsCatalog.cpp index 3c1e42ae0..4dc91ace6 100644 --- a/dDashboardServer/routes/SettingsCatalog.cpp +++ b/dDashboardServer/routes/SettingsCatalog.cpp @@ -458,6 +458,7 @@ namespace { c.Add(Float(UGC, "glow_emissive", "Glow emissive strength", "With the glow shader on: the glow shapes' material emissive, how far the emissive shader goes from lit to the plain color (1: fully).", "1", 0, 10)); c.Add(Text(UGC, "metal_material_types", "Metal material types", "Materials.xml MaterialTypes drawn as metal, comma separated (none: only LU Toolbox's metallic colors).", "shinySteel")); c.Add(Text(UGC, "brushed_material_types", "Brushed steel material types", "Materials.xml MaterialTypes drawn as brushed steel, comma separated (none: no such colors).", "brushedSteel,matteSteel")); + c.Add(Text(UGC, "brushed_colors", "Brushed steel colors", "LEGO color ids drawn as brushed steel whatever their Materials.xml type, comma separated (e.g. the drum lacquered 298,300,1002,1004). Empty: none.", "")); c.Add(Bool(UGC, "remove_hidden_faces", "Remove faces nobody can see", "", true)); c.Add(Bool(UGC, "hsr_ground_plane", "Nothing seen from below", "Also removes what can only be seen from under the model.", false)); c.Add(Unit(Int(UGC, "optimize_resolution", "Detail of the visibility renders", "", "1024", 64, 4096), "pixels")); diff --git a/dUgcServer/UgcModel.cpp b/dUgcServer/UgcModel.cpp index 21b05c9c0..3f2e22b8c 100644 --- a/dUgcServer/UgcModel.cpp +++ b/dUgcServer/UgcModel.cpp @@ -191,6 +191,7 @@ namespace UgcModel { } eLook LookOf(uint32_t id, const UgcBricks::Material& material, const LookRules& rules) { + if (const auto color = rules.colors.find(id); color != rules.colors.end()) return color->second; if (rules.paletteGlow && UgcPalette::Glow(id)) return eLook::GLOW; if (rules.paletteMetallic && UgcPalette::IsMetallic(id)) return eLook::METAL; const auto type = rules.materialTypes.find(material.type); diff --git a/dUgcServer/UgcModel.h b/dUgcServer/UgcModel.h index 990f2f84a..301d16100 100644 --- a/dUgcServer/UgcModel.h +++ b/dUgcServer/UgcModel.h @@ -75,9 +75,11 @@ namespace UgcModel { /** * Which colors have which look, from the client's data: a Materials.xml MaterialType (brickdb.zip) and LU Toolbox's - * metallic and glow colors (UgcPalette). Glow wins over metal; transparent bricks are always plastic. + * metallic and glow colors (UgcPalette), and colors named in the settings. A named color wins, then glow over metal; + * transparent bricks are always plastic. */ struct LookRules { + std::map colors; // LEGO color ids given a look by the settings (brushed_colors) std::map materialTypes{ { "shinySteel", eLook::METAL }, { "brushedSteel", eLook::BRUSHED }, { "matteSteel", eLook::BRUSHED } }; bool paletteMetallic{ true }; // LU Toolbox's Metallic colors (UgcPalette::IsMetallic) are METAL bool paletteGlow{ true }; // its glow colors (UgcPalette::Glow) are GLOW diff --git a/dUgcServer/UgcServer.cpp b/dUgcServer/UgcServer.cpp index a2f9c54d4..c87a5ce03 100644 --- a/dUgcServer/UgcServer.cpp +++ b/dUgcServer/UgcServer.cpp @@ -106,6 +106,15 @@ namespace { if (!type.empty() && type != "none") settings.build.looks.materialTypes[type] = look; } } + // LEGO color ids drawn as brushed steel whatever their Materials.xml type + { + std::stringstream stream(Game::config->GetValue("brushed_colors")); + std::string id; + while (std::getline(stream, id, ',')) { + std::erase_if(id, [](unsigned char c) { return std::isspace(c); }); + if (const auto color = GeneralUtils::TryParse(id)) settings.build.looks.colors[*color] = UgcModel::eLook::BRUSHED; + } + } 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/docs/UgcServer.md b/docs/UgcServer.md index b59c1c0e5..09e821b9c 100644 --- a/docs/UgcServer.md +++ b/docs/UgcServer.md @@ -221,11 +221,12 @@ all of its levels, so each look needs a group of its own. | Setting (`ugcconfig.ini`, dashboard: UGC models) | Default | What it writes | | --- | --- | --- | | `shader_metal` | 0 (off) | `S_Metal_Model` for metal colors: 88 is Polished Metal (gameValue 98). The client loads `textures/metal/metal_reflection_polished.dds` itself and tints it by the vertex color (`Metallic.fx`, `Technique_Lighting_PolishedMetal_VertColor`). | -| `shader_brushed` | 0 (off) | `S_Brushed_Model` for brushed steel colors: 89 is Brushed Steel (gameValue 99; it loads `metal_reflection_brushed.dds` and `_noise.dds`, the noise in object space). The client's Materials.xml has no such colors, so this only does something with a Materials.xml that names them. | +| `shader_brushed` | 0 (off) | `S_Brushed_Model` for brushed steel colors: 89 is Brushed Steel (gameValue 99; it loads `metal_reflection_brushed.dds` and `_noise.dds`, the noise in object space). The textures are registered by the client (0x00453730) as global shader textures 6 and 7, so the .nif needs none. The client's Materials.xml has no brushed types, so this needs `brushed_colors` or a Materials.xml that names them. | | `shader_glow` | 0 (off) | `S_Glow_Model` for opaque glowing colors: 46 is LEGO-Emissive (gameValue 53), which draws `lerp(lit, vertex color, vertex alpha * material emissive red)`, opaque. | | `glow_emissive` | 1 | The glow shapes' `NiMaterialProperty` emissive (grey): how far the shader goes from lit to the plain color. | | `metal_material_types` | `shinySteel` | Materials.xml `MaterialType`s that are metal (empty: the default; `none`: none). | | `brushed_material_types` | `brushedSteel,matteSteel` | Materials.xml `MaterialType`s that are brushed steel. | +| `brushed_colors` | empty | LEGO color ids that are brushed steel whatever their type (e.g. the drum lacquered 298, 300, 1002, 1004); they win over the metal and glow colors. | Which color has which look is data, not a list in the code (`UgcModel::LookOf`): glow is LU Toolbox's glow table (`UgcPalette::Glow`: 50, 294, 329, 9000-9027), metal is LU Toolbox's metallic table (`UgcPalette::IsMetallic`) plus diff --git a/resources/ugcconfig.ini b/resources/ugcconfig.ini index 3ea60c6d2..ebcf86489 100644 --- a/resources/ugcconfig.ini +++ b/resources/ugcconfig.ini @@ -81,6 +81,8 @@ shader_glow=0 glow_emissive=1 metal_material_types=shinySteel brushed_material_types=brushedSteel,matteSteel +# brushed_colors: LEGO color ids drawn as brushed steel whatever their type (e.g. the drum lacquered 298,300,1002,1004) +brushed_colors= # 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 6de99012f..104c0626b 100644 --- a/tests/dUgcTests/UgcTests.cpp +++ b/tests/dUgcTests/UgcTests.cpp @@ -1148,6 +1148,12 @@ TEST(UgcShaders, LooksComeFromTheColorData) { EXPECT_EQ(UgcModel::LookOf(329, plastic, rules), UgcModel::eLook::GLOW); // LU Toolbox's glow colors EXPECT_EQ(UgcModel::LookOf(50, plastic, rules), UgcModel::eLook::GLOW); EXPECT_EQ(UgcModel::LookOf(9016, plastic, rules), UgcModel::eLook::GLOW); + UgcModel::LookRules named; + named.colors[298] = UgcModel::eLook::BRUSHED; + named.colors[329] = UgcModel::eLook::BRUSHED; + EXPECT_EQ(UgcModel::LookOf(298, steel, named), UgcModel::eLook::BRUSHED); // a named color wins over its type + EXPECT_EQ(UgcModel::LookOf(329, plastic, named), UgcModel::eLook::BRUSHED); // and over the glow colors + EXPECT_EQ(UgcModel::LookOf(150, steel, named), UgcModel::eLook::METAL); UgcModel::LookRules none; none.materialTypes.clear(); none.paletteMetallic = false;