fix(wireframe): 扫描结果按字段路径缓存并每帧合并,补齐炮塔与车体漏采
This commit is contained in:
1 parent
41b94ef0f9
commit
aa3bcba363
3 files changed
+114
-24
No files matched your search
@@ -2051,11 +2051,41 @@
|
|||||||
return true;
|
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) {
|
function ScanMeshNodes(tank, nodes, stats, Seen) {
|
||||||
Seen = Seen || new WeakSet();
|
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;
|
if (!IsSceneNode(V) || Seen.has(V)) return;
|
||||||
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
|
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
|
||||||
|
Srcs.push({ Comp: Comp, Path: Path });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
tank.qz1_1.s2a_1.forEach(function (VL) {
|
tank.qz1_1.s2a_1.forEach(function (VL) {
|
||||||
@@ -2064,22 +2094,23 @@
|
|||||||
for (var K in comp) {
|
for (var K in comp) {
|
||||||
var V = comp[K];
|
var V = comp[K];
|
||||||
if (!IsPlainObj(V)) continue;
|
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)
|
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
|
||||||
try {
|
try {
|
||||||
for (var K2 in V) {
|
for (var K2 in V) {
|
||||||
var V2 = V[K2];
|
var V2 = V[K2];
|
||||||
if (!IsPlainObj(V2)) continue;
|
if (!IsPlainObj(V2)) continue;
|
||||||
if (IsSceneNode(V2)) { Try(V2); continue; }
|
if (IsSceneNode(V2)) { Try(V2, comp, [K, K2]); continue; }
|
||||||
for (var K3 in V2) {
|
for (var K3 in V2) {
|
||||||
var V3 = V2[K3];
|
var V3 = V2[K3];
|
||||||
if (IsPlainObj(V3)) Try(V3);
|
if (IsPlainObj(V3)) Try(V3, comp, [K, K2, K3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
NodeSrcCache.set(tank, Srcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
|
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
|
||||||
@@ -2137,10 +2168,9 @@
|
|||||||
});
|
});
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
// ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
|
// ③ 结构扫描:无条件补充漏采(车身根/部件表往往只覆盖一部分)
|
||||||
if (nodes.length === 0) {
|
// 重复实例(皮肤树/场景树同 tp 同名)由 DedupNodes 收掉,不会再出现"两个身体"
|
||||||
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
|
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
|
||||||
}
|
|
||||||
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
|
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
|
||||||
var turretNode = GetTurretNode(tank);
|
var turretNode = GetTurretNode(tank);
|
||||||
if (stats) stats.turretComp = !!turretNode;
|
if (stats) stats.turretComp = !!turretNode;
|
||||||
|
|||||||
+38
-8
@@ -2051,11 +2051,41 @@
|
|||||||
return true;
|
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) {
|
function ScanMeshNodes(tank, nodes, stats, Seen) {
|
||||||
Seen = Seen || new WeakSet();
|
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;
|
if (!IsSceneNode(V) || Seen.has(V)) return;
|
||||||
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
|
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
|
||||||
|
Srcs.push({ Comp: Comp, Path: Path });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
tank.qz1_1.s2a_1.forEach(function (VL) {
|
tank.qz1_1.s2a_1.forEach(function (VL) {
|
||||||
@@ -2064,22 +2094,23 @@
|
|||||||
for (var K in comp) {
|
for (var K in comp) {
|
||||||
var V = comp[K];
|
var V = comp[K];
|
||||||
if (!IsPlainObj(V)) continue;
|
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)
|
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
|
||||||
try {
|
try {
|
||||||
for (var K2 in V) {
|
for (var K2 in V) {
|
||||||
var V2 = V[K2];
|
var V2 = V[K2];
|
||||||
if (!IsPlainObj(V2)) continue;
|
if (!IsPlainObj(V2)) continue;
|
||||||
if (IsSceneNode(V2)) { Try(V2); continue; }
|
if (IsSceneNode(V2)) { Try(V2, comp, [K, K2]); continue; }
|
||||||
for (var K3 in V2) {
|
for (var K3 in V2) {
|
||||||
var V3 = V2[K3];
|
var V3 = V2[K3];
|
||||||
if (IsPlainObj(V3)) Try(V3);
|
if (IsPlainObj(V3)) Try(V3, comp, [K, K2, K3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
NodeSrcCache.set(tank, Srcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
|
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
|
||||||
@@ -2137,10 +2168,9 @@
|
|||||||
});
|
});
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
// ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
|
// ③ 结构扫描:无条件补充漏采(车身根/部件表往往只覆盖一部分)
|
||||||
if (nodes.length === 0) {
|
// 重复实例(皮肤树/场景树同 tp 同名)由 DedupNodes 收掉,不会再出现"两个身体"
|
||||||
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
|
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
|
||||||
}
|
|
||||||
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
|
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
|
||||||
var turretNode = GetTurretNode(tank);
|
var turretNode = GetTurretNode(tank);
|
||||||
if (stats) stats.turretComp = !!turretNode;
|
if (stats) stats.turretComp = !!turretNode;
|
||||||
|
|||||||
@@ -526,11 +526,41 @@
|
|||||||
return true;
|
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) {
|
function ScanMeshNodes(tank, nodes, stats, Seen) {
|
||||||
Seen = Seen || new WeakSet();
|
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;
|
if (!IsSceneNode(V) || Seen.has(V)) return;
|
||||||
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
|
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
|
||||||
|
Srcs.push({ Comp: Comp, Path: Path });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
tank.qz1_1.s2a_1.forEach(function (VL) {
|
tank.qz1_1.s2a_1.forEach(function (VL) {
|
||||||
@@ -539,22 +569,23 @@
|
|||||||
for (var K in comp) {
|
for (var K in comp) {
|
||||||
var V = comp[K];
|
var V = comp[K];
|
||||||
if (!IsPlainObj(V)) continue;
|
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)
|
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
|
||||||
try {
|
try {
|
||||||
for (var K2 in V) {
|
for (var K2 in V) {
|
||||||
var V2 = V[K2];
|
var V2 = V[K2];
|
||||||
if (!IsPlainObj(V2)) continue;
|
if (!IsPlainObj(V2)) continue;
|
||||||
if (IsSceneNode(V2)) { Try(V2); continue; }
|
if (IsSceneNode(V2)) { Try(V2, comp, [K, K2]); continue; }
|
||||||
for (var K3 in V2) {
|
for (var K3 in V2) {
|
||||||
var V3 = V2[K3];
|
var V3 = V2[K3];
|
||||||
if (IsPlainObj(V3)) Try(V3);
|
if (IsPlainObj(V3)) Try(V3, comp, [K, K2, K3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
NodeSrcCache.set(tank, Srcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
|
// 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
|
||||||
@@ -612,10 +643,9 @@
|
|||||||
});
|
});
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
// ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
|
// ③ 结构扫描:无条件补充漏采(车身根/部件表往往只覆盖一部分)
|
||||||
if (nodes.length === 0) {
|
// 重复实例(皮肤树/场景树同 tp 同名)由 DedupNodes 收掉,不会再出现"两个身体"
|
||||||
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
|
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
|
||||||
}
|
|
||||||
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
|
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
|
||||||
var turretNode = GetTurretNode(tank);
|
var turretNode = GetTurretNode(tank);
|
||||||
if (stats) stats.turretComp = !!turretNode;
|
if (stats) stats.turretComp = !!turretNode;
|
||||||
|
|||||||
Reference in new issue
Block a user