fix(wireframe): 扫描结果按字段路径缓存并每帧合并,补齐炮塔与车体漏采

This commit is contained in:
TermiNexus committed 2026-10-19 22:52:33 +08:00
1 parent 41b94ef0f9
commit aa3bcba363
3 files changed
+114 -24

No files matched your search

@@ -2051,11 +2051,41 @@
return true;
}
// 扫描结果按「组件字段路径」缓存(不缓存节点对象:引擎会重建节点树,
// 但字段名与组件实例稳定,路径每次都能解析到当前节点)
var NodeSrcCache = new WeakMap(); // entity -> [{ Comp, Path: [K1, K2, K3] }]
var ScanTick = 0;
function ResolveSrc(Src) {
var V = Src.Comp;
for (var i = 0; i < Src.Path.length; i++) {
if (!V || typeof V !== 'object') return null;
V = V[Src.Path[i]];
}
return V;
}
function ScanMeshNodes(tank, nodes, stats, Seen) {
Seen = Seen || new WeakSet();
function Try(V) {
// ① 有缓存路径:直接解析(开销极小);每 300 帧强制重扫一次,捡起后加载的部件
ScanTick++;
var NeedRescan = (ScanTick % 300) === 0;
var Cached = NodeSrcCache.get(tank);
if (!NeedRescan && Cached && Cached.length) {
var AllOk = true;
for (var c = 0; c < Cached.length; c++) {
var V0 = ResolveSrc(Cached[c]);
if (!IsSceneNode(V0)) { AllOk = false; break; }
if (!Seen.has(V0)) CollectMeshNodes(V0, nodes, 0, 'scan', stats, Seen);
}
if (AllOk) return;
}
// ② 缓存失效或首次:全量扫描并重建路径表
var Srcs = [];
function Try(V, Comp, Path) {
if (!IsSceneNode(V) || Seen.has(V)) return;
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
Srcs.push({ Comp: Comp, Path: Path });
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
@@ -2064,22 +2094,23 @@
for (var K in comp) {
var V = comp[K];
if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; }
if (IsSceneNode(V)) { Try(V, comp, [K]); continue; }
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
try {
for (var K2 in V) {
var V2 = V[K2];
if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; }
if (IsSceneNode(V2)) { Try(V2, comp, [K, K2]); continue; }
for (var K3 in V2) {
var V3 = V2[K3];
if (IsPlainObj(V3)) Try(V3);
if (IsPlainObj(V3)) Try(V3, comp, [K, K2, K3]);
}
}
} catch (e) {}
}
});
} catch (e) {}
NodeSrcCache.set(tank, Srcs);
}
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
@@ -2137,10 +2168,9 @@
});
} catch(e) {}
}
// ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
if (nodes.length === 0) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
}
// ③ 结构扫描:无条件补充漏采(车身根/部件表往往只覆盖一部分)
// 重复实例(皮肤树/场景树同 tp 同名)由 DedupNodes 收掉,不会再出现"两个身体"
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
var turretNode = GetTurretNode(tank);
if (stats) stats.turretComp = !!turretNode;
+38 -8
View File
@@ -2051,11 +2051,41 @@
return true;
}
// 扫描结果按「组件字段路径」缓存(不缓存节点对象:引擎会重建节点树,
// 但字段名与组件实例稳定,路径每次都能解析到当前节点)
var NodeSrcCache = new WeakMap(); // entity -> [{ Comp, Path: [K1, K2, K3] }]
var ScanTick = 0;
function ResolveSrc(Src) {
var V = Src.Comp;
for (var i = 0; i < Src.Path.length; i++) {
if (!V || typeof V !== 'object') return null;
V = V[Src.Path[i]];
}
return V;
}
function ScanMeshNodes(tank, nodes, stats, Seen) {
Seen = Seen || new WeakSet();
function Try(V) {
// ① 有缓存路径:直接解析(开销极小);每 300 帧强制重扫一次,捡起后加载的部件
ScanTick++;
var NeedRescan = (ScanTick % 300) === 0;
var Cached = NodeSrcCache.get(tank);
if (!NeedRescan && Cached && Cached.length) {
var AllOk = true;
for (var c = 0; c < Cached.length; c++) {
var V0 = ResolveSrc(Cached[c]);
if (!IsSceneNode(V0)) { AllOk = false; break; }
if (!Seen.has(V0)) CollectMeshNodes(V0, nodes, 0, 'scan', stats, Seen);
}
if (AllOk) return;
}
// ② 缓存失效或首次:全量扫描并重建路径表
var Srcs = [];
function Try(V, Comp, Path) {
if (!IsSceneNode(V) || Seen.has(V)) return;
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
Srcs.push({ Comp: Comp, Path: Path });
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
@@ -2064,22 +2094,23 @@
for (var K in comp) {
var V = comp[K];
if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; }
if (IsSceneNode(V)) { Try(V, comp, [K]); continue; }
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
try {
for (var K2 in V) {
var V2 = V[K2];
if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; }
if (IsSceneNode(V2)) { Try(V2, comp, [K, K2]); continue; }
for (var K3 in V2) {
var V3 = V2[K3];
if (IsPlainObj(V3)) Try(V3);
if (IsPlainObj(V3)) Try(V3, comp, [K, K2, K3]);
}
}
} catch (e) {}
}
});
} catch (e) {}
NodeSrcCache.set(tank, Srcs);
}
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
@@ -2137,10 +2168,9 @@
});
} catch(e) {}
}
// ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
if (nodes.length === 0) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
}
// ③ 结构扫描:无条件补充漏采(车身根/部件表往往只覆盖一部分)
// 重复实例(皮肤树/场景树同 tp 同名)由 DedupNodes 收掉,不会再出现"两个身体"
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
var turretNode = GetTurretNode(tank);
if (stats) stats.turretComp = !!turretNode;
+38 -8
View File
@@ -526,11 +526,41 @@
return true;
}
// 扫描结果按「组件字段路径」缓存(不缓存节点对象:引擎会重建节点树,
// 但字段名与组件实例稳定,路径每次都能解析到当前节点)
var NodeSrcCache = new WeakMap(); // entity -> [{ Comp, Path: [K1, K2, K3] }]
var ScanTick = 0;
function ResolveSrc(Src) {
var V = Src.Comp;
for (var i = 0; i < Src.Path.length; i++) {
if (!V || typeof V !== 'object') return null;
V = V[Src.Path[i]];
}
return V;
}
function ScanMeshNodes(tank, nodes, stats, Seen) {
Seen = Seen || new WeakSet();
function Try(V) {
// ① 有缓存路径:直接解析(开销极小);每 300 帧强制重扫一次,捡起后加载的部件
ScanTick++;
var NeedRescan = (ScanTick % 300) === 0;
var Cached = NodeSrcCache.get(tank);
if (!NeedRescan && Cached && Cached.length) {
var AllOk = true;
for (var c = 0; c < Cached.length; c++) {
var V0 = ResolveSrc(Cached[c]);
if (!IsSceneNode(V0)) { AllOk = false; break; }
if (!Seen.has(V0)) CollectMeshNodes(V0, nodes, 0, 'scan', stats, Seen);
}
if (AllOk) return;
}
// ② 缓存失效或首次:全量扫描并重建路径表
var Srcs = [];
function Try(V, Comp, Path) {
if (!IsSceneNode(V) || Seen.has(V)) return;
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
Srcs.push({ Comp: Comp, Path: Path });
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
@@ -539,22 +569,23 @@
for (var K in comp) {
var V = comp[K];
if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; }
if (IsSceneNode(V)) { Try(V, comp, [K]); continue; }
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
try {
for (var K2 in V) {
var V2 = V[K2];
if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; }
if (IsSceneNode(V2)) { Try(V2, comp, [K, K2]); continue; }
for (var K3 in V2) {
var V3 = V2[K3];
if (IsPlainObj(V3)) Try(V3);
if (IsPlainObj(V3)) Try(V3, comp, [K, K2, K3]);
}
}
} catch (e) {}
}
});
} catch (e) {}
NodeSrcCache.set(tank, Srcs);
}
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
@@ -612,10 +643,9 @@
});
} catch(e) {}
}
// ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
if (nodes.length === 0) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
}
// ③ 结构扫描:无条件补充漏采(车身根/部件表往往只覆盖一部分)
// 重复实例(皮肤树/场景树同 tp 同名)由 DedupNodes 收掉,不会再出现"两个身体"
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
var turretNode = GetTurretNode(tank);
if (stats) stats.turretComp = !!turretNode;