mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
The fleck texture was 50 soft round blobs of one size, bright in the middle and fading out, which read as a smudgy dot pattern. LEGO's glitter bricks have many small flat flakes (about half a millimetre) of which most look faint and a few catch the light. The flecks are now flat flakes of glitter_fleck_size (0.05 model units, i.e. 0.5 mm; 0.7 to 1.3 of it) with a one-pixel edge, each as bright as its facet catches the light (0.3 to 1 of glitter_fleck_opacity, 80%, weighted towards dim), 80 a tile by default. The texture grows (128 to 512) to keep a fleck 3 pixels wide. New settings glitter_fleck_size and glitter_fleck_opacity; glitter_density defaults to 80. Only glitter output changes. Check in game: reprocess a glitter model; close up, the flecks are small crisp flakes of varied brightness, not blurry dots; from a few metres the brick still reads as its color with a fine glitter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
144 lines
6.8 KiB
C++
144 lines
6.8 KiB
C++
#include "UgcGlitter.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace UgcGlitter {
|
|
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); }
|
|
// The side of a square texture's alpha
|
|
int Side(const std::vector<uint8_t>& alpha) { return static_cast<int>(std::lround(std::sqrt(static_cast<double>(alpha.size())))); }
|
|
}
|
|
|
|
int Params::TextureSize() const {
|
|
const float wanted = 3.0f * std::max(tile, 0.001f) / std::max(fleckSize, 0.001f);
|
|
int side = 128;
|
|
while (side < 512 && static_cast<float>(side) < wanted) side *= 2;
|
|
return side;
|
|
}
|
|
|
|
std::vector<uint8_t> FleckAlpha(const Params& params) {
|
|
const int N = params.TextureSize();
|
|
std::vector<uint8_t> alpha(static_cast<size_t>(N) * N, 0);
|
|
const float size = params.fleckSize / std::max(params.tile, 0.001f) * static_cast<float>(N);
|
|
const float opacity = std::clamp(params.fleckOpacity, 0.0f, 100.0f) / 100.0f;
|
|
uint64_t state = 0x6C69747465720000ull;
|
|
const auto next = [&state] { return Unit(SplitMix(state++)); };
|
|
for (uint32_t i = 0; i < params.flecks; i++) {
|
|
const float cx = next() * N, cy = next() * N;
|
|
const float radius = std::max(size * (0.35f + next() * 0.3f), 0.6f);
|
|
const float facet = next();
|
|
const float peak = opacity * (0.3f + 0.7f * facet * facet);
|
|
const int reach = static_cast<int>(std::ceil(radius + 0.5f));
|
|
for (int dy = -reach; dy <= reach; dy++) {
|
|
for (int dx = -reach; dx <= reach; dx++) {
|
|
const int x = static_cast<int>(std::floor(cx)) + dx, y = static_cast<int>(std::floor(cy)) + dy;
|
|
const float ddx = x + 0.5f - cx, ddy = y + 0.5f - cy;
|
|
const float cover = std::clamp(radius + 0.5f - std::sqrt(ddx * ddx + ddy * ddy), 0.0f, 1.0f);
|
|
if (cover <= 0.0f) continue;
|
|
auto& value = alpha[static_cast<size_t>(((y % N) + N) % N) * N + ((x % N) + N) % N];
|
|
value = std::max(value, static_cast<uint8_t>(std::lround(cover * peak * 255.0f)));
|
|
}
|
|
}
|
|
}
|
|
return alpha;
|
|
}
|
|
|
|
float Params::SparkleTile() const {
|
|
return 75.0f * std::max(sparkleSize, 0.001f) * std::max(speed, 0.01f);
|
|
}
|
|
|
|
int Params::SparkleTextureSize() const {
|
|
// A sparkle 3 pixels wide: side = 3 * tile / size (225 at speed 1)
|
|
const float wanted = 3.0f * SparkleTile() / std::max(sparkleSize, 0.001f);
|
|
int side = 128;
|
|
while (side < 1024 && static_cast<float>(side) < wanted) side *= 2;
|
|
return side;
|
|
}
|
|
|
|
std::vector<uint8_t> SparkleAlpha(const Params& params) {
|
|
const int N = params.SparkleTextureSize();
|
|
std::vector<uint8_t> alpha(static_cast<size_t>(N) * N, 0);
|
|
const float radius = std::max(params.sparkleSize / params.SparkleTile() * static_cast<float>(N) * 0.5f, 0.75f);
|
|
const float share = std::clamp(params.sparkleAmount, 0.0f, 100.0f) / 100.0f;
|
|
const auto count = static_cast<uint32_t>(std::lround(share * static_cast<float>(N) * static_cast<float>(N) / (3.14159265f * radius * radius)));
|
|
uint64_t state = 0x737061726B6C6500ull;
|
|
const auto next = [&state] { return Unit(SplitMix(state++)); };
|
|
const int reach = static_cast<int>(std::ceil(radius + 0.5f));
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
const float cx = next() * N, cy = next() * N;
|
|
for (int dy = -reach; dy <= reach; dy++) {
|
|
for (int dx = -reach; dx <= reach; dx++) {
|
|
const int x = static_cast<int>(std::floor(cx)) + dx, y = static_cast<int>(std::floor(cy)) + dy;
|
|
const float ddx = x + 0.5f - cx, ddy = y + 0.5f - cy;
|
|
// Flat, with a pixel's worth of edge
|
|
const float cover = std::clamp(radius + 0.5f - std::sqrt(ddx * ddx + ddy * ddy), 0.0f, 1.0f);
|
|
if (cover <= 0.0f) continue;
|
|
auto& value = alpha[static_cast<size_t>(((y % N) + N) % N) * N + ((x % N) + N) % N];
|
|
value = std::max(value, static_cast<uint8_t>(std::lround(cover * SPARKLE_ALPHA)));
|
|
}
|
|
}
|
|
}
|
|
return alpha;
|
|
}
|
|
|
|
std::vector<std::vector<uint8_t>> Mipmaps(const std::vector<uint8_t>& alpha, int keepPeaks) {
|
|
std::vector<std::vector<uint8_t>> levels{ alpha };
|
|
for (int size = Side(alpha) / 2, level = 0; size >= 1; size /= 2, level++) {
|
|
const auto& above = levels.back();
|
|
const int from = size * 2;
|
|
std::vector<uint8_t> next(static_cast<size_t>(size) * size);
|
|
for (int y = 0; y < size; y++) {
|
|
for (int x = 0; x < size; x++) {
|
|
const auto at = [&](int dx, int dy) { return static_cast<int>(above[static_cast<size_t>(y * 2 + dy) * from + x * 2 + dx]); };
|
|
next[static_cast<size_t>(y) * size + x] = static_cast<uint8_t>(level < keepPeaks ? std::max({ at(0, 0), at(1, 0), at(0, 1), at(1, 1) }) :
|
|
(at(0, 0) + at(1, 0) + at(0, 1) + at(1, 1) + 2) / 4);
|
|
}
|
|
}
|
|
levels.push_back(std::move(next));
|
|
}
|
|
return levels;
|
|
}
|
|
|
|
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, eLayer layer) {
|
|
const auto a = glm::abs(normal);
|
|
const float scale = 1.0f / std::max(tile, 1e-3f);
|
|
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)) ^ (static_cast<uint64_t>(layer) << 40));
|
|
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;
|
|
}
|
|
|
|
glm::vec4 SparkleColor(const glm::vec4& brickColor, const Params& params) {
|
|
const float tint = std::clamp(params.sparkleTint, 0.0f, 100.0f) / 100.0f;
|
|
const float brightness = std::clamp(params.sparkleBrightness, 0.0f, 100.0f) / 100.0f;
|
|
return glm::vec4(glm::clamp(glm::mix(glm::vec3(1.0f), glm::vec3(brickColor), tint) * brightness, 0.0f, 1.0f), 1.0f);
|
|
}
|
|
|
|
float Sample(const std::vector<uint8_t>& alpha, const glm::vec2& uv) {
|
|
const int N = Side(alpha);
|
|
if (N == 0 || alpha.size() != static_cast<size_t>(N) * N) return 0.0f;
|
|
const float x = (uv.x - std::floor(uv.x)) * N - 0.5f, y = (uv.y - std::floor(uv.y)) * N - 0.5f;
|
|
const int x0 = static_cast<int>(std::floor(x)), y0 = static_cast<int>(std::floor(y));
|
|
const float fx = x - x0, fy = y - y0;
|
|
const auto at = [&](int px, int py) { return alpha[static_cast<size_t>(((py % N) + N) % N) * N + ((px % N) + N) % N] / 255.0f; };
|
|
return (at(x0, y0) * (1 - fx) + at(x0 + 1, y0) * fx) * (1 - fy) + (at(x0, y0 + 1) * (1 - fx) + at(x0 + 1, y0 + 1) * fx) * fy;
|
|
}
|
|
}
|