Files
DarkflameServer/dUgcServer/Model/UgcGlitter.h
Aaron Kimbrell fb7850fb9f fix(ugc): glitter sparkles that move on placed models
Why the glitter never moved: player models (LOT 14) are wrapped in
weeblewobble.kfm (RenderComponentWrapper 9845), so the client makes them
an LWOSkinnedRenderComponent, whose Run (0x00d6d3d0) updates the scene
graph (and so any NiTextureTransformController) only while animation is
enabled, and LWOModelBehaviorComponent::EnableAnimation (0x00be2740)
turns it off for modelType 2, which every placed property model is. The
root flags 0x102 added earlier are only read by the base render
component. Nothing in a placed model's .nif can move.

What does move: shader classes set globals in their own per-frame Run.
Distortion Directional (Ocean) (mapShaders 79, Run 0x010b90c0) slides
its texture layers by fixed shares of a tile a second, as the game's own
pond ripples (S79__pond_ripplesShape). Glitter bricks now get a sparkle
group, S79_GlitterSparkle_Model: their triangles lifted 0.005 off the
brick, vertex colors white tinted by the brick, UVs placed per brick,
alpha tested (ShaderCommon's alpha test phase, GREATEREQUAL 127), with a
stored texture of flat sparkles at alpha 230: one layer's sparkle alone
averages under the test, two meeting pass, so sparkles flash and go out
as the layers cross. The flecks stay (LEGO-AnimUV, now without the
controllers and flags that never ran). The icon and the dashboard's 3D
view leave the sparkles out.

New settings: shader_glitter_sparkle (79, 0 off), glitter_sparkle_size,
glitter_sparkle_amount, glitter_sparkle_tint, glitter_sparkle_brightness;
glitter_speed is now how fast sparkles flash (the sparkle tile). Only
glitter output changes; non-glitter models are byte-identical.

Check in game: reprocess a property with glitter models, then look at
them from a few angles and distances, on each graphics quality:
- sparkles flash on and off all over the glitter bricks, continuously
- no flickering fight between the sparkles and the brick surface
- transparent glitter bricks still see-through, flecks still visible
- nothing drawn where there is no glitter brick; icons unchanged
  apart from the flecks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 00:40:10 -05:00

87 lines
5.2 KiB
C++

#pragma once
#include <cstdint>
#include <vector>
#include <glm/glm.hpp>
/**
* The glitter the UGC server gives glitter colors (docs/UgcServer.md, "Glitter"). Pure.
*
* Flecks: 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). They stay still: a placed player model is never
* updated after it loads (LWOSkinnedRenderComponent::Run with animation off for modelType 2), so texture controllers in
* its .nif never run.
*
* Sparkles: a second shape over each glitter brick drawn by the client's Distortion Directional (Ocean) shader, whose
* texture layers the client moves every frame whatever the object does (the shader's Run sets
* g_vDirectionalMotionLayer1..3). Its texture has flat sparkles at an alpha that one layer's alone keeps under the
* alpha test, so a sparkle shows only where two moving layers' sparkles meet: points that flash and go out.
*
* Both are placed on each brick by UVs projected from the model's own coordinates, turned and moved by a number of the
* brick's own (BrickSeed), so no two bricks have the same pattern and a model made again has the same one.
*/
namespace UgcGlitter {
// The fleck texture's side in pixels (a power of two, mipmapped down to 1)
constexpr int TEXTURE_SIZE = 128;
struct Params {
// Flecks (LEGO-AnimUV)
float tile{ 1.6f }; // glitter_size: the fleck texture's side in model units (LDD units: a stud is 0.8)
uint32_t flecks{ 50 }; // glitter_density: flecks in one tile
bool random{ true }; // glitter_random: each brick its own pattern (BrickSeed), else the same on every brick
// Sparkles (Distortion Directional)
float sparkleSize{ 0.1f }; // glitter_sparkle_size: a sparkle's diameter in model units
float sparkleAmount{ 5.0f }; // glitter_sparkle_amount: percent of each moving layer covered by sparkles
float speed{ 1.0f }; // glitter_speed: how fast sparkles flash and go out (1: about half a second)
float sparkleTint{ 30.0f }; // glitter_sparkle_tint: percent, how far sparkles take their brick's color
float sparkleBrightness{ 100.0f }; // glitter_sparkle_brightness: percent, the sparkles' vertex color
// The sparkle texture's side in model units. The client moves its layers a fixed share of a tile a second (a
// tile in 24, 48 and 72 s), so the tile sets how fast they cross: 75 sparkle sizes times the speed.
float SparkleTile() const;
// The sparkle texture's side in pixels: the power of two (128 to 1024) that makes a sparkle 3 pixels wide
int SparkleTextureSize() const;
bool operator==(const Params&) const = default;
};
// The sparkles' alpha in their texture. The alpha test of the client's alpha test phase keeps what reaches 127
// (ShaderCommon__SetupPhaseRenderStates: GREATEREQUAL 0x7f); the Directional shader averages 2 layers or 3 (by the
// graphics settings), so one sparkle alone is 115 or 77 and two meeting are 230 or 153
constexpr uint8_t SPARKLE_ALPHA = 230;
// How far the sparkle shapes stand off their brick, along its normals (model units): in front of its surface, so
// the brick (drawn after them when it is transparent) doesn't cover them and they don't fight it for the depth
constexpr float SPARKLE_LIFT = 0.005f;
// The fleck texture's alpha (TEXTURE_SIZE squared, rows top to bottom): `flecks` soft dots at the same places every
// time, wrapping around the edges so the texture tiles. Its color is white.
std::vector<uint8_t> FleckAlpha(uint32_t flecks);
// The sparkle texture's alpha (SparkleTextureSize() squared): flat discs of sparkleSize at SPARKLE_ALPHA covering
// sparkleAmount percent of it, at the same places every time, tiling. Its color is white.
std::vector<uint8_t> SparkleAlpha(const Params& params);
// A square texture's mipmaps' alpha, from its own size down to 1: each the mean of 2x2 of the one before, or for
// the first `keepPeaks` the brightest of them (so sparkles keep their alpha at the next few distances)
std::vector<std::vector<uint8_t>> Mipmaps(const std::vector<uint8_t>& alpha, int keepPeaks = 0);
// 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);
// Which texture a UV set is for: a brick's sparkles are placed apart from its flecks
enum class eLayer : uint8_t { FLECKS = 0, SPARKLES };
// 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 and layer; seed 0 leaves it
// as it is
glm::vec2 Uv(const glm::vec3& position, const glm::vec3& normal, float tile, uint32_t seed = 0, eLayer layer = eLayer::FLECKS);
// A sparkle's vertex color: white taking `sparkleTint` percent of its brick's color (sRGB), at `sparkleBrightness`,
// alpha 1
glm::vec4 SparkleColor(const glm::vec4& brickColor, const Params& params);
// A square texture's alpha (0..1) at `uv` (wrapping, bilinear)
float Sample(const std::vector<uint8_t>& alpha, const glm::vec2& uv);
}