mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
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>
167 lines
7.9 KiB
C++
167 lines
7.9 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <set>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include <glm/glm.hpp>
|
|
|
|
#include "UgcBricks.h"
|
|
|
|
namespace NifFile {
|
|
struct Model;
|
|
}
|
|
|
|
/**
|
|
* Brick models as triangle meshes: LXFML parts, the mesh built from them (opaque and transparent bricks apart, with
|
|
* the material colors as vertex colors) and the mesh of a client .nif. Pure apart from BrickLibrary's file reads.
|
|
*/
|
|
namespace UgcModel {
|
|
// A brick in a model: its primitive, materials (one per geometry part) and where it is
|
|
struct Part {
|
|
uint32_t designId{};
|
|
std::vector<uint32_t> materials;
|
|
glm::mat4 transform{ 1.0f };
|
|
};
|
|
|
|
// The parts of an LXFML 5 (Bricks/Brick/Part with bones) or 4 (Scene/Model/Group/Part with axis angles) model.
|
|
// Empty with `error` set when it can't be read.
|
|
std::vector<Part> ParseLxfml(std::string_view lxfml, std::string& error);
|
|
|
|
// Whether an LXFML reads but has no bricks at all (nothing to make; not a failure)
|
|
bool HasNoBricks(std::string_view lxfml);
|
|
|
|
/**
|
|
* How a color looks in the game when the UGC server's shader settings give it a shader of its own
|
|
* (docs/UgcServer.md, "Metal and glow"): the LEGO plastic of S01_Opaque_Model, polished metal, brushed steel, glow
|
|
* or glitter. Transparent bricks are plastic or glitter.
|
|
*/
|
|
enum class eLook : uint8_t { PLASTIC = 0, METAL, BRUSHED, GLOW, GLITTER };
|
|
constexpr size_t LOOK_COUNT = 5;
|
|
|
|
struct Mesh {
|
|
std::vector<glm::vec3> positions;
|
|
std::vector<glm::vec3> normals;
|
|
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; }
|
|
bool Empty() const { return indices.empty(); }
|
|
void Append(const Mesh& other);
|
|
void Transform(const glm::mat4& transform);
|
|
};
|
|
|
|
struct Model {
|
|
Mesh opaque;
|
|
Mesh transparent;
|
|
std::vector<uint32_t> missingDesigns; // designs without geometry in the client, skipped
|
|
std::vector<size_t> transparentBricks; // where each transparent brick's triangles start in transparent.indices
|
|
size_t bricks{};
|
|
|
|
bool Empty() const { return opaque.Empty() && transparent.Empty(); }
|
|
// The bounds of every vertex; false when there are none
|
|
bool Bounds(glm::vec3& min, glm::vec3& max) const;
|
|
};
|
|
|
|
enum class ePalette {
|
|
LU_TOOLBOX, // LU Toolbox's colors (UgcPalette), what its importer colors models with
|
|
BRICKDB, // the brick database's Materials.xml
|
|
};
|
|
|
|
/**
|
|
* 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), and colors named in the settings. A named color wins, then glow over metal;
|
|
* transparent bricks are plastic unless their color is glitter.
|
|
*/
|
|
struct LookRules {
|
|
std::map<uint32_t, eLook> colors; // LEGO color ids given a look by the settings (brushed_colors, glitter_colors)
|
|
std::map<std::string, eLook> materialTypes{ { "shinySteel", eLook::METAL }, { "brushedSteel", eLook::BRUSHED }, { "matteSteel", eLook::BRUSHED },
|
|
{ "glitter", eLook::GLITTER } };
|
|
bool paletteMetallic{ true }; // LU Toolbox's Metallic colors (UgcPalette::IsMetallic) are METAL
|
|
bool paletteGlow{ true }; // its glow colors (UgcPalette::Glow) are GLOW
|
|
};
|
|
|
|
// The look of an opaque material `id` whose Materials.xml entry is `material`
|
|
eLook LookOf(uint32_t id, const UgcBricks::Material& material, const LookRules& rules);
|
|
|
|
struct BuildOptions {
|
|
ePalette palette{ ePalette::LU_TOOLBOX };
|
|
float colorVariation{ 5.0f }; // percent, 0: none (LU Toolbox: Apply Color Variation, 5%)
|
|
uint64_t seed{}; // of the variation's random numbers
|
|
float transparentOpacity{ 58.82f }; // percent, transparent bricks' vertex alpha (LU Toolbox palette only)
|
|
float brightness{ 100.0f }; // percent, the models' vertex colors (not icons'); 100: as the palette has them
|
|
std::set<uint32_t> transparentColors; // color ids drawn transparent whatever Materials.xml says (129: its alpha is 255)
|
|
bool icon{}; // the icon renderer's color corrections
|
|
uint32_t lod{}; // brickprimitives level
|
|
LookRules looks; // which colors are metal and glow (Mesh::looks)
|
|
// Satin (opal) colors: transparent bricks of these colors get satinOpacity (percent) as their vertex alpha
|
|
// instead of the transparent opacity, and every brick of them has its color moved satinWhiten percent towards
|
|
// white (milky). The client has no satin shader: they stay in the transparent group. Empty: none.
|
|
std::set<uint32_t> satinColors;
|
|
float satinOpacity{ 75.0f };
|
|
float satinWhiten{ 20.0f };
|
|
};
|
|
|
|
/**
|
|
* The mesh of a model's parts, colored as LU Toolbox's Process Model does: a brick is transparent only when all of
|
|
* its materials are, each material of each brick has its brightness shifted by the color variation (the same
|
|
* random number for a brick's material in every LOD and every time), vertex colors are sRGB with alpha 1 for
|
|
* opaque bricks and the transparent opacity for transparent ones.
|
|
*/
|
|
Model Build(const std::vector<Part>& parts, UgcBricks::BrickLibrary& library, const BuildOptions& options = {});
|
|
|
|
/**
|
|
* The distance range (near, far) of each LOD LU Toolbox makes, for the brickprimitives levels in `used` (0 to 3),
|
|
* from its settings (lod0..lod3, cull): its setup_lod_data, which picks the ranges by which levels are there.
|
|
* {0, 0} for a level it has no range for.
|
|
*/
|
|
struct LodDistances {
|
|
float lod0{ 0.0f };
|
|
float lod1{ 50.0f };
|
|
float lod2{ 100.0f };
|
|
float lod3{ 280.0f };
|
|
float cull{ 10000.0f };
|
|
};
|
|
std::vector<std::pair<float, float>> LodRanges(const std::vector<uint32_t>& used, const LodDistances& distances);
|
|
|
|
/**
|
|
* Splits a mesh the way LU Toolbox's divide_mesh does while it has too many vertices (or triangles): at the mean
|
|
* of its vertices along its longest side, keeping connected pieces whole. Falls back to Split when that can't
|
|
* divide it.
|
|
*/
|
|
std::vector<Mesh> Divide(const Mesh& mesh, size_t maxVertices = 65535, size_t maxTriangles = 65535);
|
|
|
|
// A client .nif's meshes as one model (vertex colors times material color; transparent when blended). `tagLooks`:
|
|
// the look of the opaque shapes whose multishader tag (NifFile::ShaderTag, a mapShaders id) is listed, and of the
|
|
// transparent ones when it is GLITTER. `overlayTags`: alpha tested shapes with these tags are left out (the UGC
|
|
// server's glitter sparkles, drawn over the glitter bricks)
|
|
Model FromNif(const NifFile::Model& nif, const std::map<int32_t, eLook>& tagLooks = {}, const std::set<int32_t>& overlayTags = {});
|
|
|
|
/**
|
|
* The mesh's triangles by look ([eLook] -> its triangles; a triangle's look is its first vertex's), the looks not
|
|
* in `separate` staying with PLASTIC. nullopt when nothing is separated: the mesh stays as it is.
|
|
*/
|
|
std::optional<std::array<Mesh, LOOK_COUNT>> SplitLooks(const Mesh& mesh, const std::array<bool, LOOK_COUNT>& separate);
|
|
|
|
// The mesh cut into pieces at these index offsets (each piece's triangles start at one), e.g. one per brick
|
|
std::vector<Mesh> SplitAt(const Mesh& mesh, const std::vector<size_t>& starts);
|
|
|
|
// Keeps the triangles whose flag is set (and the vertices they use)
|
|
void KeepTriangles(Mesh& mesh, const std::vector<bool>& keep);
|
|
|
|
// Splits a mesh into pieces the .nif format can hold (at most `maxVertices` vertices and `maxTriangles` triangles)
|
|
std::vector<Mesh> Split(const Mesh& mesh, size_t maxVertices = 65535, size_t maxTriangles = 65535);
|
|
}
|