mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
fix(dashboard): 3D views never draw a kept manifest with the game shaders
The game shader views looked up each shader's technique in the manifest's "techniques". Property scenery manifests are cached by browsers for a day (world ones for an hour), so after the update a browser drew the property view from the manifest the older server had sent, which has no techniques: every shader fell back to LEGO, whose decal texture alpha laid the see-through tree, rock and water textures over white vertex colors. Nimbus Isle came out with white trees, rocks and water, a yellow build surface and a solid white build border. - Manifest URLs carry the conversion format the views are written for (?format=5, scenery-core.js SCENERY_FORMAT), so a kept manifest from an older server is never used; SceneryCoreJs checks it matches Scenery.cpp FORMAT_VERSION. - A manifest without techniques (an older server's) is drawn with the viewer's own lights and its textureAlpha table instead of every shader guessed as LEGO. - A material whose NiAlphaController animates its alpha is drawn at its highest key. The AnimAlpha shaders now use the material alpha, and effects resting at 0 in the file (the Venture Explorer's lightning) had vanished. Conversion format 5. Checked by rendering the world view of every zone with models and the property view of every property template (headless, fixed cameras) before and after, and the Nimbus Isle property with a manifest stripped of its techniques, which reproduced the white look. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,5 +87,6 @@ target_compile_definitions(dWebTests PRIVATE DLU_SOURCE_DIR="${PROJECT_SOURCE_DI
|
||||
find_program(NODE_EXECUTABLE node)
|
||||
if(NODE_EXECUTABLE)
|
||||
add_test(NAME SceneryCoreJs COMMAND ${NODE_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/scenery-core.test.mjs"
|
||||
"${PROJECT_SOURCE_DIR}/dDashboardServer/static/js/scenery-core.js" "${PROJECT_SOURCE_DIR}/dCommon/NifFile.h")
|
||||
"${PROJECT_SOURCE_DIR}/dDashboardServer/static/js/scenery-core.js" "${PROJECT_SOURCE_DIR}/dCommon/NifFile.h"
|
||||
"${PROJECT_SOURCE_DIR}/dDashboardServer/routes/Scenery.cpp")
|
||||
endif()
|
||||
|
||||
@@ -213,6 +213,36 @@ TEST(NifFileTests, PassesPropertiesDownTheTree) {
|
||||
EXPECT_TRUE(m.doubleSided);
|
||||
}
|
||||
|
||||
// A material whose NiAlphaController animates its alpha (flickering effects that rest at 0 in the file) is drawn at
|
||||
// its highest key; one without keeps its own alpha
|
||||
TEST(NifFileTests, DrawsAnAnimatedAlphaAtItsHighest) {
|
||||
for (const bool animated : { true, false }) {
|
||||
NifBuilder nif;
|
||||
const auto root = nif.Add("NiNode", {});
|
||||
const auto material = nif.Add("NiMaterialProperty", {});
|
||||
const auto controller = nif.Add("NiAlphaController", {});
|
||||
const auto interpolator = nif.Add("NiFloatInterpolator", {});
|
||||
const auto keys = nif.Add("NiFloatData", Bytes().Put<uint32_t>(3).Put<uint32_t>(1).Floats({ 0.0f, 0.0f, 0.5f, 0.8f, 1.0f, 0.0f }));
|
||||
// NiObjectNET with the controller, ambient, diffuse, specular, emissive, glossiness, alpha 0
|
||||
Bytes body;
|
||||
body.Put<uint32_t>(0xFFFFFFFF).Put<uint32_t>(0).Put<int32_t>(animated ? controller : -1);
|
||||
body.Floats({ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 10.0f, 0.0f });
|
||||
nif.Set(material, body);
|
||||
// NiTimeController: next, flags, frequency, phase, start, stop, target; the interpolator
|
||||
nif.Set(controller, Bytes().Put<int32_t>(-1).Put<uint16_t>(8).Floats({ 1, 0, 0, 1 }).Put<int32_t>(material).Put<int32_t>(interpolator));
|
||||
nif.Set(interpolator, Bytes().Put(0.0f).Put<int32_t>(keys));
|
||||
const auto shape = nif.Add("NiTriShape", {});
|
||||
const auto data = nif.Add("NiTriShapeData", TriShapeData());
|
||||
nif.Set(root, Node(Av(0, { 0, 0, 0 }, IDENTITY, 1.0f, { material }), { shape }));
|
||||
nif.Set(shape, Geometry(Av(0, { 0, 0, 0 }, IDENTITY, 1.0f, {}), data));
|
||||
std::string error;
|
||||
const auto model = NifFile::Parse(nif.Build({ root }), 0, error);
|
||||
ASSERT_TRUE(model) << error;
|
||||
ASSERT_EQ(model->meshes.size(), 1u);
|
||||
EXPECT_FLOAT_EQ(model->meshes[0].material.alpha, animated ? 0.8f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Two layer shaders use NiTexturingProperty's dark texture too, each texture on the UV set its flags name
|
||||
TEST(NifFileTests, ReadsTheDarkTextureAndEachTexturesUvSet) {
|
||||
NifBuilder nif;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// The 3D views' shader lookups (static/js/scenery-core.js): which technique draws a mesh and what it uses.
|
||||
// Run by ctest: node scenery-core.test.mjs <scenery-core.js> [NifFile.h]
|
||||
// Run by ctest: node scenery-core.test.mjs <scenery-core.js> [NifFile.h] [Scenery.cpp]
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const [modulePath, nifHeader] = process.argv.slice(2);
|
||||
const [modulePath, nifHeader, sceneryRoutes] = process.argv.slice(2);
|
||||
const S = await import(pathToFileURL(modulePath).href);
|
||||
let failures = 0;
|
||||
const same = (actual, expected, what) => {
|
||||
@@ -58,6 +58,19 @@ same(pick(S.gameLook(manifest, 2, { ...colored, vertexColors: 0 }), BASICS), { f
|
||||
// Without the zone's lighting the viewer lights scenery itself
|
||||
same(S.gameLook({ ...manifest, lighting: null }, 0, colored), null, 'no lighting');
|
||||
|
||||
// A manifest from before the techniques (format 3, or one a browser kept): no shader is guessed as LEGO. The views
|
||||
// light it themselves and its textureAlpha table says what texture alpha does, so a Basic tree's see-through leaves
|
||||
// aren't laid over its white vertex colors
|
||||
const format3 = { ...manifest, format: 3, techniques: undefined, textureAlpha: { 5: 'decal' } };
|
||||
same(S.gameShaded(format3), false, 'format 3 manifest is not game shaded');
|
||||
same(S.gameShaded(manifest), true, 'manifest with techniques is game shaded');
|
||||
same(S.gameLook(format3, 0, colored), null, 'format 3 manifest: viewer lights');
|
||||
same(S.textureAlphaMode(format3, 0, {}), 'opacity', 'format 3 manifest: Basic VC texture alpha');
|
||||
same(S.textureAlphaMode(format3, 1, { shaderTag: 1 }), 'decal', 'format 3 manifest: LEGO texture alpha');
|
||||
// Manifest URLs name the format the views are written for, so an older manifest a browser kept isn't used
|
||||
same(S.manifestUrl('/api/world3d/1200/scenery'), '/api/world3d/1200/scenery?format=' + S.SCENERY_FORMAT, 'manifest URL');
|
||||
same(S.manifestUrl('/a?b=1'), '/a?b=1&format=' + S.SCENERY_FORMAT, 'manifest URL with a query');
|
||||
|
||||
// What the flags turn into: moving textures, both sides, blending, not drawn
|
||||
const flagged = (flags, family = 'basic') => S.gameLook({ ...manifest, shaders: [1], techniques: { 1: { family, look: 0, alpha: 'opacity', flags } } }, 0, colored);
|
||||
same(flagged(T.UV_ANIM).uvAnim, true, 'UV animation');
|
||||
@@ -137,6 +150,12 @@ if (nifHeader) {
|
||||
same(bitsOf('eShaderLook'), L, 'SHADER_LOOK matches eShaderLook');
|
||||
}
|
||||
|
||||
// The format the views are written for is the server's conversion format (Scenery.cpp FORMAT_VERSION)
|
||||
if (sceneryRoutes) {
|
||||
const format = readFileSync(sceneryRoutes, 'utf8').match(/constexpr uint32_t FORMAT_VERSION = (\d+);/);
|
||||
same(format && Number(format[1]), S.SCENERY_FORMAT, 'SCENERY_FORMAT matches FORMAT_VERSION');
|
||||
}
|
||||
|
||||
// The near plane grows with the distance, within limits
|
||||
same([S.nearPlaneFor(10), S.nearPlaneFor(2000), S.nearPlaneFor(100000)], [0.5, 5, 20], 'near plane');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user