mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
fix(ugc): each glitter brick gets its own fleck pattern
Every glitter brick had the same flecks in the same places: the UVs were the vertex positions projected on an axis plane, so bricks a whole tile apart (and every brick of the same shape at the same spot in its own model) looked identical. Each brick now has a number of its own (UgcGlitter::BrickSeed, from the model's id and the brick's index, kept per vertex in Mesh::brickSeeds) that turns the projection by an angle and moves it by an offset under a tile, differently for each axis plane. A model made again gets the same patterns; every LOD of a brick the same one. The icon now draws the flecks on the UVs the .nif has (Mesh::uvs read back by FromNif). New setting glitter_random (1; 0 puts the same pattern on every brick as before). Non-glitter models are byte-identical (hash tests unchanged). Check in game: reprocess a model with several glitter bricks of the same shape; the fleck patterns differ from brick to brick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -480,6 +480,7 @@ namespace {
|
||||
c.Add(Float(UGC, "glitter_size", "Glitter tile size", "The fleck texture's tile in model units (a stud is 0.8): how far apart the flecks are, the same on every brick.", "1.6", 0.1f, 100));
|
||||
c.Add(Int(UGC, "glitter_density", "Glitter flecks", "Flecks in one tile of the glitter texture.", "50", 0, 2000));
|
||||
c.Add(Float(UGC, "glitter_speed", "Glitter speed", "How fast the flecks drift: 1 moves them a tile in 7 s one way and 11 s the other; 0 keeps them still.", "1", 0, 100));
|
||||
c.Add(Bool(UGC, "glitter_random", "Glitter placed per brick", "Each glitter brick gets its own fleck pattern (turned and moved by a number of the brick's own, the same every time the model is made); off: the same pattern on every brick.", true));
|
||||
c.Add(Text(UGC, "satin_colors", "Satin colors", "Satin (opal) color ids, comma separated: they stay transparent plastic (the client has no satin shader) but are made milky and less see-through. By default LEGO's satin colors 360,362,363,364,365,366,367,376 (none: off)." + notLive, "360,362,363,364,365,366,367,376"));
|
||||
c.Add(Float(UGC, "satin_opacity", "Satin opacity", "Percent: the opacity of transparent satin bricks, instead of the transparent opacity.", "75", 0, 100));
|
||||
c.Add(Float(UGC, "satin_whiten", "Satin whitening", "Percent: how far satin colors go towards white.", "20", 0, 100));
|
||||
|
||||
@@ -144,7 +144,8 @@ namespace {
|
||||
out.I32(-1); // collision object
|
||||
}
|
||||
|
||||
// `glitter`: with a UV set projected for the glitter texture (UgcGlitter::Uv)
|
||||
// `glitter`: with a UV set projected for the glitter texture (UgcGlitter::Uv), placed by each vertex's brick
|
||||
// (Mesh::brickSeeds) when the glitter is random
|
||||
std::string TriShapeData(const UgcModel::Mesh& mesh, const UgcGlitter::Params* glitter = nullptr) {
|
||||
Writer out;
|
||||
const auto count = static_cast<uint16_t>(mesh.positions.size());
|
||||
@@ -191,7 +192,8 @@ namespace {
|
||||
}
|
||||
if (uvs) {
|
||||
for (size_t v = 0; v < mesh.positions.size(); v++) {
|
||||
const auto uv = UgcGlitter::Uv(mesh.positions[v], mesh.normals[v], glitter->tile);
|
||||
const uint32_t seed = glitter->random && v < mesh.brickSeeds.size() ? mesh.brickSeeds[v] : 0;
|
||||
const auto uv = UgcGlitter::Uv(mesh.positions[v], mesh.normals[v], glitter->tile, seed);
|
||||
out.Float(uv.x);
|
||||
out.Float(uv.y);
|
||||
}
|
||||
|
||||
@@ -53,12 +53,33 @@ namespace UgcGlitter {
|
||||
return levels;
|
||||
}
|
||||
|
||||
glm::vec2 Uv(const glm::vec3& position, const glm::vec3& normal, float tile) {
|
||||
namespace {
|
||||
uint64_t SplitMix(uint64_t x) {
|
||||
x += 0x9E3779B97F4A7C15ull;
|
||||
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ull;
|
||||
x = (x ^ (x >> 27)) * 0x94D049BB133111EBull;
|
||||
return x ^ (x >> 31);
|
||||
}
|
||||
// 0..1 from 24 bits of a hash
|
||||
float Unit(uint64_t bits) { return static_cast<float>(bits >> 40) / static_cast<float>(1ull << 24); }
|
||||
}
|
||||
|
||||
uint32_t BrickSeed(uint64_t modelSeed, uint32_t brick) {
|
||||
const auto hash = SplitMix(SplitMix(modelSeed ^ 0x676C6974746572ull) + brick);
|
||||
return static_cast<uint32_t>(hash >> 32) | 1u;
|
||||
}
|
||||
|
||||
glm::vec2 Uv(const glm::vec3& position, const glm::vec3& normal, float tile, uint32_t seed) {
|
||||
const auto a = glm::abs(normal);
|
||||
const float scale = 1.0f / std::max(tile, 1e-3f);
|
||||
if (a.x >= a.y && a.x >= a.z) return glm::vec2(position.z, position.y) * scale;
|
||||
if (a.y >= a.z) return glm::vec2(position.x, position.z) * scale;
|
||||
return glm::vec2(position.x, position.y) * scale;
|
||||
const int plane = a.x >= a.y && a.x >= a.z ? 0 : a.y >= a.z ? 1 : 2;
|
||||
const glm::vec2 uv = (plane == 0 ? glm::vec2(position.z, position.y) : plane == 1 ? glm::vec2(position.x, position.z) : glm::vec2(position.x, position.y)) * scale;
|
||||
if (seed == 0) return uv;
|
||||
const auto hash = SplitMix((static_cast<uint64_t>(seed) << 2) | static_cast<uint64_t>(plane));
|
||||
const float angle = Unit(hash) * 6.28318530718f;
|
||||
const glm::vec2 offset(Unit(SplitMix(hash)), Unit(SplitMix(hash + 1)));
|
||||
const float c = std::cos(angle), s = std::sin(angle);
|
||||
return glm::vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y) + offset;
|
||||
}
|
||||
|
||||
float Sample(const std::vector<uint8_t>& alpha, const glm::vec2& uv) {
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
* The glitter the UGC server gives glitter colors (docs/UgcServer.md, "Metal and glow"): a tileable texture of white
|
||||
* flecks (its alpha) laid over the brick's color by the client's LEGO-AnimUV shader (lerp(vertex color, texture,
|
||||
* texture alpha), then the LEGO lighting), on UVs projected from the model's own coordinates so every brick gets the
|
||||
* same density, drifting as the texture transform's translation loops. Pure.
|
||||
* same density, turned and moved by a number of each brick's own (BrickSeed) so no two bricks have the same pattern,
|
||||
* drifting as the texture transform's translation loops. Pure.
|
||||
*/
|
||||
namespace UgcGlitter {
|
||||
// The texture's side in pixels (a power of two, mipmapped down to 1)
|
||||
@@ -19,6 +20,7 @@ namespace UgcGlitter {
|
||||
float tile{ 1.6f }; // glitter_size: the texture's side in model units (LDD units: a stud is 0.8)
|
||||
uint32_t flecks{ 50 }; // glitter_density: flecks in one tile
|
||||
float speed{ 1.0f }; // glitter_speed: 1 moves the flecks a tile in U in 7 s and in V in 11 s; 0 keeps them still
|
||||
bool random{ true }; // glitter_random: each brick its own pattern (BrickSeed), else the same on every brick
|
||||
|
||||
// Seconds the texture's translation takes to go one tile in U and in V (0: no animation)
|
||||
float PeriodU() const { return speed > 0.0f ? 7.0f / speed : 0.0f; }
|
||||
@@ -33,8 +35,13 @@ namespace UgcGlitter {
|
||||
// The texture's mipmaps' alpha, from TEXTURE_SIZE down to 1 (each the mean of 2x2 of the one before)
|
||||
std::vector<std::vector<uint8_t>> Mipmaps(const std::vector<uint8_t>& alpha);
|
||||
|
||||
// A vertex's UV: its position on the axis plane its normal faces most, in tiles
|
||||
glm::vec2 Uv(const glm::vec3& position, const glm::vec3& normal, float tile);
|
||||
// A brick's number for placing its glitter (never 0), from the model's seed and the brick's index: the same for
|
||||
// the brick in every LOD and every time the model is made
|
||||
uint32_t BrickSeed(uint64_t modelSeed, uint32_t brick);
|
||||
|
||||
// A vertex's UV: its position on the axis plane its normal faces most, in tiles, turned by an angle and moved by
|
||||
// an offset (under a tile) that `seed` (the brick's BrickSeed) picks for each plane; seed 0 leaves it as it is
|
||||
glm::vec2 Uv(const glm::vec3& position, const glm::vec3& normal, float tile, uint32_t seed = 0);
|
||||
|
||||
// The texture's alpha (0..1) at `uv` (wrapping, bilinear)
|
||||
float Sample(const std::vector<uint8_t>& alpha, const glm::vec2& uv);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "NifFile.h"
|
||||
#include "UgcGlitter.h"
|
||||
#include "UgcPalette.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
@@ -164,6 +165,16 @@ namespace UgcModel {
|
||||
if (other.looks.empty()) looks.resize(positions.size(), eLook::PLASTIC);
|
||||
else looks.insert(looks.end(), other.looks.begin(), other.looks.end());
|
||||
}
|
||||
if (!brickSeeds.empty() || !other.brickSeeds.empty()) {
|
||||
brickSeeds.resize(base, 0);
|
||||
if (other.brickSeeds.empty()) brickSeeds.resize(positions.size(), 0);
|
||||
else brickSeeds.insert(brickSeeds.end(), other.brickSeeds.begin(), other.brickSeeds.end());
|
||||
}
|
||||
if (!uvs.empty() || !other.uvs.empty()) {
|
||||
uvs.resize(base, glm::vec2(0.0f));
|
||||
if (other.uvs.empty()) uvs.resize(positions.size(), glm::vec2(0.0f));
|
||||
else uvs.insert(uvs.end(), other.uvs.begin(), other.uvs.end());
|
||||
}
|
||||
indices.reserve(indices.size() + other.indices.size());
|
||||
for (const auto index : other.indices) indices.push_back(base + index);
|
||||
}
|
||||
@@ -232,6 +243,7 @@ namespace UgcModel {
|
||||
continue;
|
||||
}
|
||||
model.bricks++;
|
||||
const auto brickSeed = UgcGlitter::BrickSeed(options.seed, brick);
|
||||
const auto materialOf = [&part, &library](size_t index) {
|
||||
auto id = index < part.materials.size() ? part.materials[index] : (part.materials.empty() ? 0 : part.materials[0]);
|
||||
// Unknown colors are black in LU Toolbox (its name included, so black's variation too). A color LU
|
||||
@@ -306,6 +318,7 @@ namespace UgcModel {
|
||||
mesh.colors.push_back(color);
|
||||
if (&mesh == &model.opaque) model.opaque.glow.push_back(glow);
|
||||
mesh.looks.push_back(look);
|
||||
mesh.brickSeeds.push_back(brickSeed);
|
||||
}
|
||||
for (const auto i : geometry.indices) mesh.indices.push_back(base + i);
|
||||
}
|
||||
@@ -363,6 +376,7 @@ namespace UgcModel {
|
||||
glm::vec4 color = materialColor;
|
||||
if (vertexColors) color *= glm::vec4(source.colors[v * 4], source.colors[v * 4 + 1], source.colors[v * 4 + 2], source.colors[v * 4 + 3]) / 255.0f;
|
||||
mesh.colors.push_back(color);
|
||||
if (source.uvs.size() == count * 2) mesh.uvs.emplace_back(source.uvs[v * 2], source.uvs[v * 2 + 1]);
|
||||
}
|
||||
mesh.indices.assign(source.indices.begin(), source.indices.end());
|
||||
// Normals from the faces when the file has none
|
||||
@@ -406,6 +420,8 @@ namespace UgcModel {
|
||||
if (source < mesh.colors.size()) piece.colors.push_back(mesh.colors[source]);
|
||||
if (source < mesh.glow.size()) piece.glow.push_back(mesh.glow[source]);
|
||||
if (source < mesh.looks.size()) piece.looks.push_back(mesh.looks[source]);
|
||||
if (source < mesh.brickSeeds.size()) piece.brickSeeds.push_back(mesh.brickSeeds[source]);
|
||||
if (source < mesh.uvs.size()) piece.uvs.push_back(mesh.uvs[source]);
|
||||
}
|
||||
piece.indices.push_back(it->second);
|
||||
}
|
||||
@@ -428,6 +444,8 @@ namespace UgcModel {
|
||||
if (source < mesh.colors.size()) kept.colors.push_back(mesh.colors[source]);
|
||||
if (source < mesh.glow.size()) kept.glow.push_back(mesh.glow[source]);
|
||||
if (source < mesh.looks.size()) kept.looks.push_back(mesh.looks[source]);
|
||||
if (source < mesh.brickSeeds.size()) kept.brickSeeds.push_back(mesh.brickSeeds[source]);
|
||||
if (source < mesh.uvs.size()) kept.uvs.push_back(mesh.uvs[source]);
|
||||
}
|
||||
kept.indices.push_back(remap[source]);
|
||||
}
|
||||
@@ -461,6 +479,8 @@ namespace UgcModel {
|
||||
if (source < mesh.colors.size()) current.colors.push_back(mesh.colors[source]);
|
||||
if (source < mesh.glow.size()) current.glow.push_back(mesh.glow[source]);
|
||||
if (source < mesh.looks.size()) current.looks.push_back(mesh.looks[source]);
|
||||
if (source < mesh.brickSeeds.size()) current.brickSeeds.push_back(mesh.brickSeeds[source]);
|
||||
if (source < mesh.uvs.size()) current.uvs.push_back(mesh.uvs[source]);
|
||||
}
|
||||
current.indices.push_back(it->second);
|
||||
}
|
||||
@@ -514,6 +534,8 @@ namespace UgcModel {
|
||||
if (source < mesh.colors.size()) half.colors.push_back(mesh.colors[source]);
|
||||
if (source < mesh.glow.size()) half.glow.push_back(mesh.glow[source]);
|
||||
if (source < mesh.looks.size()) half.looks.push_back(mesh.looks[source]);
|
||||
if (source < mesh.brickSeeds.size()) half.brickSeeds.push_back(mesh.brickSeeds[source]);
|
||||
if (source < mesh.uvs.size()) half.uvs.push_back(mesh.uvs[source]);
|
||||
}
|
||||
half.indices.push_back(remap[source]);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ namespace UgcModel {
|
||||
std::vector<glm::vec4> colors; // sRGB, 0..1, alpha is opacity
|
||||
std::vector<glm::vec3> glow; // linear glow color per vertex (LU Toolbox's "Glow" layer); empty when nothing glows
|
||||
std::vector<eLook> looks; // per vertex; empty when everything is plastic (transparent meshes: plastic or glitter)
|
||||
// Per vertex: its brick's UgcGlitter::BrickSeed, which places the brick's glitter; empty when not known (a
|
||||
// mesh read from a .nif)
|
||||
std::vector<uint32_t> brickSeeds;
|
||||
// Per vertex: the UV set of a mesh read from a .nif (its glitter's, placed when it was made); empty otherwise
|
||||
std::vector<glm::vec2> uvs;
|
||||
std::vector<uint32_t> indices;
|
||||
|
||||
size_t TriangleCount() const { return indices.size() / 3; }
|
||||
|
||||
@@ -384,7 +384,10 @@ namespace UgcRender {
|
||||
const auto look = mesh.looks.size() == mesh.positions.size() && (isOpaque || mesh.looks[i0] == UgcModel::eLook::GLITTER) ? mesh.looks[i0] : UgcModel::eLook::PLASTIC;
|
||||
if (look == UgcModel::eLook::GLITTER) {
|
||||
// LEGO-AnimUV: lerp(vertex color, the texture's white, its alpha), then lit as plastic
|
||||
const float fleck = UgcGlitter::Sample(glitterAlpha, UgcGlitter::Uv(position, normal, options.glitter.tile));
|
||||
// On the mesh's own UVs (read from the .nif: each brick's pattern placed as it was made), else projected
|
||||
const auto uv = mesh.uvs.size() == mesh.positions.size() ? mesh.uvs[i0] * w0 + mesh.uvs[i1] * w1 + mesh.uvs[i2] * w2 :
|
||||
UgcGlitter::Uv(position, normal, options.glitter.tile);
|
||||
const float fleck = UgcGlitter::Sample(glitterAlpha, uv);
|
||||
base = glm::vec4(glm::mix(glm::vec3(base), glm::vec3(1.0f), fleck), base.a);
|
||||
}
|
||||
if (look == UgcModel::eLook::PLASTIC || look == UgcModel::eLook::GLITTER) {
|
||||
|
||||
@@ -122,6 +122,7 @@ namespace {
|
||||
settings.shaders.glitterParams.tile = std::clamp(Setting<float>("glitter_size", 1.6f), 0.1f, 100.0f);
|
||||
settings.shaders.glitterParams.flecks = std::min(Setting<uint32_t>("glitter_density", 50), 2000u);
|
||||
settings.shaders.glitterParams.speed = std::clamp(Setting<float>("glitter_speed", 1.0f), 0.0f, 100.0f);
|
||||
settings.shaders.glitterParams.random = Setting<int32_t>("glitter_random", 1) != 0;
|
||||
// Which Materials.xml MaterialTypes are metal, brushed steel and glitter
|
||||
for (const auto& [key, look] : { std::pair{ "metal_material_types", UgcModel::eLook::METAL }, std::pair{ "brushed_material_types", UgcModel::eLook::BRUSHED },
|
||||
std::pair{ "glitter_material_types", UgcModel::eLook::GLITTER } }) {
|
||||
|
||||
@@ -306,6 +306,7 @@ all of its levels, so each look needs a group of its own.
|
||||
| `glitter_size` | 1.6 | The glitter texture's tile, in model units (a stud is 0.8): the flecks' spacing, the same on every brick. |
|
||||
| `glitter_density` | 50 | Flecks in one tile. |
|
||||
| `glitter_speed` | 1 | How fast the flecks drift: a tile in U in 7 s and in V in 11 s at 1; 0 keeps them still (no controllers). |
|
||||
| `glitter_random` | 1 | Each glitter brick its own fleck pattern (turned and moved by the brick); 0: the same pattern on every brick. |
|
||||
| `satin_colors` | 360,362,363,364,365,366,367,376 | Satin (opal) colors, see Satin below. The default: LEGO's color data's "Satin Colors" category (the Transparent ... Opal colors). Empty: the default; `none`: off. |
|
||||
| `satin_opacity` | 75 | Percent: the vertex alpha of transparent satin bricks, instead of `transparent_opacity` or the Materials.xml alpha. |
|
||||
| `satin_whiten` | 20 | Percent: how far satin colors are moved towards white (in linear RGB, after the color variation). |
|
||||
@@ -352,7 +353,11 @@ flecks on a brick that is otherwise lit as plastic, and moving the texture trans
|
||||
What a glitter shape has, beside what plastic shapes have (white material, alpha, specular, vertex colors):
|
||||
|
||||
- A UV set: each vertex's position on the axis plane its normal faces most, divided by `glitter_size`
|
||||
(`UgcGlitter::Uv`), so the flecks are as dense on every brick and every side.
|
||||
(`UgcGlitter::Uv`), so the flecks are as dense on every brick and every side, then turned by an angle and moved by
|
||||
an offset under a tile that the brick picks for each plane (`glitter_random`, on by default): each brick's number
|
||||
(`UgcGlitter::BrickSeed`, from the model's id and the brick's index, kept per vertex in `Mesh::brickSeeds`), so
|
||||
no two bricks have the same pattern, every LOD of a brick has its own, and a model made again gets the same. The
|
||||
icon draws the flecks on the UVs the .nif has (`Mesh::uvs`, read back by `UgcModel::FromNif`).
|
||||
- An `NiTexturingProperty` (one per file, shared by both glitter groups): apply mode decal (fixed function would do
|
||||
what the shader does), 9 slots, the base map only: wrap S and T, trilinear, UV set 0, a texture transform
|
||||
(translation 0, scale 1, Maya method, center 0.5).
|
||||
|
||||
@@ -95,6 +95,7 @@ brushed_colors=298,300,1002,1004
|
||||
# over the color and moves it. 0: off, glitter stays plastic.
|
||||
# glitter_size: the fleck texture's tile in model units (a stud is 0.8); glitter_density: flecks in a tile;
|
||||
# glitter_speed: 1 moves the flecks a tile in 7 s one way and 11 s the other, 0 keeps them still.
|
||||
# glitter_random: 1 places each brick's flecks its own way (turned and moved by the brick), 0 the same on every brick.
|
||||
# glitter_colors: LEGO color ids that are glitter whatever their type (empty: the default, 114,117, which LEGO's color
|
||||
# data calls glitter; none: no colors)
|
||||
shader_glitter=21
|
||||
@@ -103,6 +104,7 @@ glitter_colors=114,117
|
||||
glitter_size=1.6
|
||||
glitter_density=50
|
||||
glitter_speed=1
|
||||
glitter_random=1
|
||||
# Satin (opal) colors stay transparent plastic (the client has no satin shader) but are made milky:
|
||||
# satin_colors: color ids (empty: the default, LEGO's satin colors 360,362,363,364,365,366,367,376; none: off),
|
||||
# satin_opacity: their transparent bricks' opacity in percent (instead of transparent_opacity),
|
||||
|
||||
@@ -1551,6 +1551,74 @@ TEST(UgcShaders, GlitterGroups) {
|
||||
EXPECT_EQ(off.stats.find("groups"), std::string::npos);
|
||||
}
|
||||
|
||||
// Each glitter brick gets its own fleck pattern: bricks a whole number of tiles apart (whose projected UVs are the same
|
||||
// but for whole tiles) get different ones, the same model made again the same ones, another model others;
|
||||
// glitter_random 0 puts the same pattern on every brick as before
|
||||
TEST(UgcShaders, GlitterIsPlacedPerBrick) {
|
||||
// Brick seeds: never 0, different bricks and models different, the same brick the same
|
||||
EXPECT_NE(UgcGlitter::BrickSeed(7, 0), 0u);
|
||||
EXPECT_EQ(UgcGlitter::BrickSeed(7, 3), UgcGlitter::BrickSeed(7, 3));
|
||||
EXPECT_NE(UgcGlitter::BrickSeed(7, 3), UgcGlitter::BrickSeed(7, 4));
|
||||
EXPECT_NE(UgcGlitter::BrickSeed(7, 3), UgcGlitter::BrickSeed(8, 3));
|
||||
// Seed 0 is the plain projection; a seed turns and moves it
|
||||
const glm::vec3 p(0.4f, 0.8f, 0.2f), q(1.2f, 0.8f, 0.2f);
|
||||
EXPECT_EQ(UgcGlitter::Uv(p, { 0, 0, 1 }, 1.6f, 0), UgcGlitter::Uv(p, { 0, 0, 1 }, 1.6f));
|
||||
const auto a = UgcGlitter::Uv(p, { 0, 0, 1 }, 1.6f, 12345), b = UgcGlitter::Uv(q, { 0, 0, 1 }, 1.6f, 12345);
|
||||
EXPECT_NE(a, UgcGlitter::Uv(p, { 0, 0, 1 }, 1.6f));
|
||||
EXPECT_NEAR(glm::length(b - a), 0.5f, 1e-5f); // turned and moved, not stretched: still 0.8 / 1.6 tiles apart
|
||||
EXPECT_NE(UgcGlitter::Uv(p, { 0, 0, 1 }, 1.6f, 12345), UgcGlitter::Uv(p, { 0, 0, 1 }, 1.6f, 54321));
|
||||
|
||||
UgcBricks::BrickLibrary library(MakeRes(), 0);
|
||||
library.SetMaterials({ { 5001, { 67, 84, 147, 150, "glitter" } } });
|
||||
// Three transparent glitter bricks 3.2 apart (two tiles of 1.6)
|
||||
const std::string lxfml = R"(<LXFML versionMajor="5"><Bricks>
|
||||
<Brick><Part designID="3001" materials="5001"><Bone transformation="1,0,0,0,1,0,0,0,1,0,0,0"/></Part></Brick>
|
||||
<Brick><Part designID="3001" materials="5001"><Bone transformation="1,0,0,0,1,0,0,0,1,3.2,0,0"/></Part></Brick>
|
||||
<Brick><Part designID="3001" materials="5001"><Bone transformation="1,0,0,0,1,0,0,0,1,6.4,0,0"/></Part></Brick>
|
||||
</Bricks></LXFML>)";
|
||||
auto settings = SmallSettings();
|
||||
settings.shaders.glitter = 21;
|
||||
// Each glitter shape's UVs' fractions (the texture wraps), LOD 0
|
||||
const auto patterns = [&](const UgcJobs::Outcome& outcome) {
|
||||
std::string error;
|
||||
const auto read = NifFile::Parse(*ZCompression::Gunzip(outcome.files.at("model.nif.gz")), 0, error);
|
||||
EXPECT_TRUE(read) << error;
|
||||
std::vector<std::vector<float>> out;
|
||||
for (const auto& mesh : read->meshes) {
|
||||
if (mesh.material.shaderTag != 21) continue;
|
||||
std::vector<float> fractions;
|
||||
for (const auto uv : mesh.uvs) fractions.push_back(std::round((uv - std::floor(uv)) * 1000.0f) / 1000.0f);
|
||||
out.push_back(fractions);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
const auto random = UgcJobs::ProcessModel(lxfml, library, settings, 7);
|
||||
ASSERT_TRUE(random.ok) << random.error;
|
||||
const auto perBrick = patterns(random);
|
||||
ASSERT_EQ(perBrick.size(), 3u); // one shape per transparent brick
|
||||
EXPECT_NE(perBrick[0], perBrick[1]);
|
||||
EXPECT_NE(perBrick[1], perBrick[2]);
|
||||
EXPECT_NE(perBrick[0], perBrick[2]);
|
||||
// The same model made again: the same file; another model (seed) with the same bricks: other patterns
|
||||
EXPECT_EQ(random.files.at("model.nif.checksum"), UgcJobs::ProcessModel(lxfml, library, settings, 7).files.at("model.nif.checksum"));
|
||||
EXPECT_NE(patterns(UgcJobs::ProcessModel(lxfml, library, settings, 8)), perBrick);
|
||||
// Off: the same pattern on every brick, as before
|
||||
settings.shaders.glitterParams.random = false;
|
||||
const auto off = UgcJobs::ProcessModel(lxfml, library, settings, 7);
|
||||
const auto same = patterns(off);
|
||||
ASSERT_EQ(same.size(), 3u);
|
||||
EXPECT_EQ(same[0], same[1]);
|
||||
EXPECT_EQ(same[1], same[2]);
|
||||
|
||||
// The icon draws the flecks where the .nif has them (its UVs, read back), so it changes with the placement
|
||||
std::string error;
|
||||
const auto read = NifFile::Parse(*ZCompression::Gunzip(random.files.at("model.nif.gz")), 0, error);
|
||||
ASSERT_TRUE(read) << error;
|
||||
const auto back = UgcModel::FromNif(*read, settings.shaders.TagLooks());
|
||||
EXPECT_EQ(back.transparent.uvs.size(), back.transparent.positions.size());
|
||||
EXPECT_NE(random.files.at("icon.png"), off.files.at("icon.png"));
|
||||
}
|
||||
|
||||
// Glitter in the icon: the texture's flecks over the color before the light, where they are at the start
|
||||
TEST(UgcShaders, IconsDrawGlitterFlecks) {
|
||||
UgcModel::Model model;
|
||||
|
||||
Reference in New Issue
Block a user