mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-11 07:13:44 +00:00
feat(ugc): glitter flecks (LEGO-AnimUV) and milky satin in made models
Glitter colors (Materials.xml type glitter, glitter_colors 114,117) go into S21_Glitter_Model and, transparent, S21_GlitterAlpha_Model (shader_glitter, default 21, LEGO-AnimUV). Their shapes get box-projected UVs, an NiTexturingProperty with a stored 128 px mipmapped fleck texture (NiSourceTexture + NiPersistentSrcTextureRendererData, as the client's own env_ag_ocean-maelstrom.nif) and two NiTextureTransformControllers looping the base map's translation (glitter_size, glitter_density, glitter_speed). The shader lays the texture over the vertex color by its alpha and outputs the vertex alpha, so transparent glitter blends as S01_Alpha does. Satin colors (satin_colors, LEGO's opal colors) stay in S01_Alpha but get satin_opacity and are whitened by satin_whiten. NifFile reads the base map's scroll speed (uvScroll) from the controllers; the icon draws still flecks, the UGC 3D view and the LXFML viewers moving ones. stats.json counts the glitter groups. With shader_glitter 0 and no satin colors the files are the same bytes as before (tested). Also keeps glow_emissive for the icon (it was reset by the icon settings). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,7 @@ namespace {
|
||||
|
||||
struct NetHeader {
|
||||
std::string name;
|
||||
int32_t controller{ -1 };
|
||||
};
|
||||
|
||||
struct AvHeader {
|
||||
@@ -327,7 +328,7 @@ namespace {
|
||||
net.name = String(reader.U32());
|
||||
const auto extra = reader.U32();
|
||||
reader.Skip(static_cast<uint64_t>(extra) * 4);
|
||||
reader.I32(); // controller
|
||||
net.controller = reader.I32();
|
||||
return net;
|
||||
}
|
||||
|
||||
@@ -554,6 +555,55 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// Tiles a second the controllers from `first` on (an NiTexturingProperty's) move its base map in U and V: each
|
||||
// NiTextureTransformController translating the base map, from its NiFloatInterpolator's NiFloatData's first key
|
||||
// to its last, times its frequency
|
||||
std::array<float, 2> BaseMapScroll(int32_t first) {
|
||||
std::array<float, 2> scroll{};
|
||||
std::set<int32_t> seen;
|
||||
for (int32_t index = first; index >= 0 && !seen.contains(index);) {
|
||||
seen.insert(index);
|
||||
const auto* type = TypeOf(index);
|
||||
if (!type || *type != "NiTextureTransformController") break;
|
||||
m_Used.insert(index);
|
||||
auto reader = BlockReader(index);
|
||||
const auto next = reader.I32();
|
||||
reader.U16(); // flags
|
||||
const auto frequency = reader.Float();
|
||||
reader.Skip(12); // phase, start, stop
|
||||
reader.I32(); // target
|
||||
const auto interpolator = reader.I32();
|
||||
const auto shaderMap = reader.U8();
|
||||
const auto slot = reader.U32();
|
||||
const auto operation = reader.U32();
|
||||
const auto* interpolatorType = TypeOf(interpolator);
|
||||
if (reader.Ok() && !shaderMap && slot == 0 && operation <= 1 && interpolatorType && *interpolatorType == "NiFloatInterpolator") {
|
||||
m_Used.insert(interpolator);
|
||||
auto value = BlockReader(interpolator);
|
||||
value.Float();
|
||||
const auto data = value.I32();
|
||||
const auto* dataType = TypeOf(data);
|
||||
if (value.Ok() && dataType && *dataType == "NiFloatData") {
|
||||
m_Used.insert(data);
|
||||
auto keys = BlockReader(data);
|
||||
const auto count = keys.U32();
|
||||
const auto keyType = count > 0 ? keys.U32() : 0;
|
||||
// Linear keys are time and value; quadratic add two tangents; TBC three floats
|
||||
const uint32_t floats = keyType == 1 ? 2 : keyType == 2 ? 4 : keyType == 3 ? 5 : 0;
|
||||
if (count >= 2 && floats > 0 && count <= 100000) {
|
||||
const auto values = keys.Array<float>(static_cast<uint64_t>(count) * floats);
|
||||
if (keys.Ok()) {
|
||||
const float duration = values[(count - 1) * floats] - values[0];
|
||||
if (duration > 0.0f) scroll[operation] = (values[(count - 1) * floats + 1] - values[1]) / duration * frequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
index = next;
|
||||
}
|
||||
return scroll;
|
||||
}
|
||||
|
||||
NifFile::Material ReadMaterial(const Properties& properties, uint8_t& baseSet, uint8_t& darkSet) {
|
||||
NifFile::Material material;
|
||||
if (properties.material >= 0) {
|
||||
@@ -597,7 +647,7 @@ namespace {
|
||||
}
|
||||
if (properties.texturing >= 0) {
|
||||
auto reader = BlockReader(properties.texturing);
|
||||
ReadNet(reader);
|
||||
material.uvScroll = BaseMapScroll(ReadNet(reader).controller);
|
||||
reader.U16(); // flags
|
||||
reader.U32(); // texture count
|
||||
// TexDesc (nif.xml, 20.1.0.3 on): source, TexturingMapFlags (clamp in bits 12-15, UV set in 0-7), whether a
|
||||
@@ -781,6 +831,7 @@ namespace NifFile {
|
||||
{"shaderTag", material.shaderTag}, {"darkTexture", uv2 ? darkIndex : -1}, {"uv2", uv2}
|
||||
};
|
||||
if (m < looks.size()) entry["look"] = looks[m];
|
||||
if (material.uvScroll[0] != 0.0f || material.uvScroll[1] != 0.0f) entry["uvScroll"] = { material.uvScroll[0], material.uvScroll[1] };
|
||||
Append(body, mesh.positions.data(), mesh.positions.size() * sizeof(float));
|
||||
if (!mesh.normals.empty()) {
|
||||
std::vector<int8_t> packed(mesh.normals.size());
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
* (NiNode, NiLODNode, NiBillboardNode and other nodes) with its transforms baked into the vertices, NiTriShape and
|
||||
* NiTriStrips geometry (positions, normals, the first UV set, vertex colors), and the properties Gamebryo passes down
|
||||
* the tree: NiMaterialProperty, NiAlphaProperty, NiTexturingProperty's base texture (an external NiSourceTexture),
|
||||
* NiVertexColorProperty and NiStencilProperty's draw mode (double sided). Skipped: hidden subtrees, animation,
|
||||
* NiVertexColorProperty and NiStencilProperty's draw mode (double sided), and how fast NiTextureTransformControllers move
|
||||
* the base texture. Skipped: hidden subtrees, other animation,
|
||||
* particles, lights and cameras; skinned geometry is drawn in its bind pose. Blocks are skipped by their stored sizes,
|
||||
* so a block this reader doesn't know never breaks the rest of the file.
|
||||
*/
|
||||
@@ -39,6 +40,9 @@ namespace NifFile {
|
||||
// the base texture; as `texture` and `embeddedTexture`
|
||||
std::string darkTexture;
|
||||
int32_t embeddedDarkTexture{ -1 };
|
||||
// How fast the base texture moves (tiles a second in U and V): NiTextureTransformControllers on the
|
||||
// NiTexturingProperty translating the base map, each from its first key to its last, looping
|
||||
std::array<float, 2> uvScroll{};
|
||||
int32_t shaderTag{ -1 }; // mapShaders id from a multishader tag in the name of the mesh or a node above it
|
||||
};
|
||||
|
||||
@@ -98,7 +102,11 @@ namespace NifFile {
|
||||
BRUSHED = 128,
|
||||
// LEGO-Emissive: lerp(lit, vertex color, vertex alpha * NiMaterialProperty's emissive red); the vertex alpha is
|
||||
// that mask, not opacity
|
||||
EMISSIVE = 256
|
||||
EMISSIVE = 256,
|
||||
// Not a shader's: the UGC server's glitter groups (LEGO-AnimUV with the fleck texture it stores in the .nif,
|
||||
// UgcGlitter), white flecks by the texture's alpha over the lit vertex color, moving with the texture. Set by
|
||||
// the dashboard's UGC mesh route, not by ShaderLookFor.
|
||||
GLITTER = 512
|
||||
};
|
||||
|
||||
// eShaderLook bits of a shader (mapShaders.gameValue); 0 for the usual lit look and for fixed function
|
||||
@@ -131,6 +139,7 @@ namespace NifFile {
|
||||
* to a multiple of 4), then the binary data it describes. Per mesh at "offset": float32 positions (3 per vertex),
|
||||
* int8 normals (3 per vertex, times 127, padded to 4 bytes) when "normals", float32 UVs (2 per vertex) when "uv",
|
||||
* float32 dark texture UVs when "uv2", uint8 RGBA colors when "colors", then uint16 indices (padded to 4 bytes).
|
||||
* A mesh whose base texture moves has "uvScroll" (Material::uvScroll).
|
||||
* `textures[i]` is where mesh i's texture is (empty: none) and `darkTextures[i]` its dark texture; the header
|
||||
* lists each once in "textures" and a mesh's "texture" and "darkTexture" index it (-1: none). `looks[i]`, when
|
||||
* given, is mesh i's "look" (eShaderLook bits of the shader it is drawn with, for views without a scenery manifest).
|
||||
|
||||
Reference in New Issue
Block a user