fix(wireframe): 车身改用皮肤根节点并合并结构扫描,修复漏采与只剩炮塔

This commit is contained in:
TermiNexus committed 2026-10-19 22:08:33 +08:00
1 parent 526adc760c
commit af8cde08d2
3 files changed
+123 -69

No files matched your search

@@ -1983,8 +1983,13 @@
} }
// ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) ===== // ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group, stats) { // Seen:跨「车身根 / 部件表 / 炮塔 / 扫描」多路径去重,避免同一节点被画多次
if (!bpNode || depth > 5) return; function CollectMeshNodes(bpNode, nodes, depth, group, stats, Seen) {
if (!bpNode || depth > 12) return;
if (Seen) {
if (Seen.has(bpNode)) return;
Seen.add(bpNode);
}
if (stats) stats.seen++; if (stats) stats.seen++;
var name = bpNode.g35_1 || ''; var name = bpNode.g35_1 || '';
if (bpNode.v3m_1) { if (bpNode.v3m_1) {
@@ -2023,7 +2028,7 @@
((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null); ((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null);
if (Children) { if (Children) {
ForEachInList(Children, function (child) { ForEachInList(Children, function (child) {
if (child) CollectMeshNodes(child, nodes, depth + 1, group, stats); if (child) CollectMeshNodes(child, nodes, depth + 1, group, stats, Seen);
}); });
} }
} }
@@ -2037,30 +2042,38 @@
V.r35_1 && typeof V.r35_1 === 'object'); V.r35_1 && typeof V.r35_1 === 'object');
} }
// 只遍历普通对象:TypedArray / 数组 / Map / Set 的 for-in 会产出成千上万个键,必须跳过
function IsPlainObj(V) {
if (!V || typeof V !== 'object') return false;
if (Array.isArray(V) || ArrayBuffer.isView(V)) return false;
if (typeof Map !== 'undefined' && V instanceof Map) return false;
if (typeof Set !== 'undefined' && V instanceof Set) return false;
return true;
}
function ScanMeshNodes(tank, nodes, stats, Seen) { function ScanMeshNodes(tank, nodes, stats, Seen) {
Seen = Seen || new WeakSet(); Seen = Seen || new WeakSet();
function Try(V) { function Try(V) {
if (!IsSceneNode(V) || Seen.has(V)) return; if (!IsSceneNode(V) || Seen.has(V)) return;
Seen.add(V); CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
CollectMeshNodes(V, nodes, 0, 'scan', stats);
} }
try { try {
tank.qz1_1.s2a_1.forEach(function (VL) { tank.qz1_1.s2a_1.forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL; var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!comp) return; if (!IsPlainObj(comp)) return;
for (var K in comp) { for (var K in comp) {
var V = comp[K]; var V = comp[K];
if (!V || typeof V !== 'object') continue; if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; } if (IsSceneNode(V)) { Try(V); 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 (!V2 || typeof V2 !== 'object') continue; if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; } if (IsSceneNode(V2)) { Try(V2); continue; }
for (var K3 in V2) { for (var K3 in V2) {
var V3 = V2[K3]; var V3 = V2[K3];
if (V3 && typeof V3 === 'object') Try(V3); if (IsPlainObj(V3)) Try(V3);
} }
} }
} catch (e) {} } catch (e) {}
@@ -2072,21 +2085,30 @@
// ===== 提取坦克的mesh数据 ===== // ===== 提取坦克的mesh数据 =====
function GetTankMeshData(tank, stats) { function GetTankMeshData(tank, stats) {
var nodes = []; var nodes = [];
var skin = GetHullSkin(tank, stats); var Seen = new WeakSet();
var skinComp = GetSkinComponent(tank);
var skin = (skinComp && skinComp.g159_1 && skinComp.g159_1.g17r_1) ? skinComp.g159_1 : null;
if (stats) {
stats.hullSource = skin ? 'g159_1' : (skinComp ? '有组件但无皮肤' : '无皮肤组件');
stats.hullParts = (skin && typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
}
// ① 车身根节点:皮肤组件 h159_1 是「所有部件节点」的总根(比逐个部件更完整)
if (skinComp && skinComp.h159_1) {
try { CollectMeshNodes(skinComp.h159_1, nodes, 0, 'hull', stats ? stats.hull : null, Seen); } catch (e) {}
}
// ② 部件表:某些构建下根节点不完整,仍逐个部件补一遍(Seen 自动去重)
if (skin) { if (skin) {
try { try {
if (stats) stats.hullParts = (typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
ForEachInList(skin.g17r_1, function (part) { ForEachInList(skin.g17r_1, function (part) {
if (!part) return; if (!part) return;
// 新版:part.j17r_1 是单个 BP 节点;旧版是节点列表 var j = part.j17r_1; // 新版:单个 BP 节点;旧版:节点列表
var j = part.j17r_1;
if (!j) return; if (!j) return;
if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') { if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') {
ForEachInList(j, function (bp) { ForEachInList(j, function (bp) {
if (bp) CollectMeshNodes(bp, nodes, 0, 'hull', stats ? stats.hull : null); if (bp) CollectMeshNodes(bp, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
}); });
} else { } else {
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null); CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
} }
}); });
} catch(e) {} } catch(e) {}
@@ -2095,15 +2117,11 @@
if (stats) stats.turretComp = !!turretNode; if (stats) stats.turretComp = !!turretNode;
if (turretNode) { if (turretNode) {
if (stats) stats.turretRoot = true; if (stats) stats.turretRoot = true;
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null); } catch(e) {} try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null, Seen); } catch(e) {}
}
// 结构路径没拿到任何 mesh(皮肤/炮塔节点缺失或结构不符)时,直接扫实体所有组件字段找 3D 节点
if (!skin || !turretNode || nodes.length === 0) {
var Seen = new WeakSet();
for (var ni = 0; ni < nodes.length; ni++) { if (nodes[ni] && nodes[ni].node) Seen.add(nodes[ni].node); }
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
if (stats) stats.hullSource = (stats.hullSource || '扫描');
} }
// ③ 无条件结构扫描兜底:把组件字段里能看到的 3D 节点全部补进来
// (解决「有的坦克只有点」「有的只有头没有身子」——结构字段不完整时漏采)
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
return nodes; return nodes;
} }
+41 -23
View File
@@ -1983,8 +1983,13 @@
} }
// ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) ===== // ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group, stats) { // Seen:跨「车身根 / 部件表 / 炮塔 / 扫描」多路径去重,避免同一节点被画多次
if (!bpNode || depth > 5) return; function CollectMeshNodes(bpNode, nodes, depth, group, stats, Seen) {
if (!bpNode || depth > 12) return;
if (Seen) {
if (Seen.has(bpNode)) return;
Seen.add(bpNode);
}
if (stats) stats.seen++; if (stats) stats.seen++;
var name = bpNode.g35_1 || ''; var name = bpNode.g35_1 || '';
if (bpNode.v3m_1) { if (bpNode.v3m_1) {
@@ -2023,7 +2028,7 @@
((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null); ((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null);
if (Children) { if (Children) {
ForEachInList(Children, function (child) { ForEachInList(Children, function (child) {
if (child) CollectMeshNodes(child, nodes, depth + 1, group, stats); if (child) CollectMeshNodes(child, nodes, depth + 1, group, stats, Seen);
}); });
} }
} }
@@ -2037,30 +2042,38 @@
V.r35_1 && typeof V.r35_1 === 'object'); V.r35_1 && typeof V.r35_1 === 'object');
} }
// 只遍历普通对象:TypedArray / 数组 / Map / Set 的 for-in 会产出成千上万个键,必须跳过
function IsPlainObj(V) {
if (!V || typeof V !== 'object') return false;
if (Array.isArray(V) || ArrayBuffer.isView(V)) return false;
if (typeof Map !== 'undefined' && V instanceof Map) return false;
if (typeof Set !== 'undefined' && V instanceof Set) return false;
return true;
}
function ScanMeshNodes(tank, nodes, stats, Seen) { function ScanMeshNodes(tank, nodes, stats, Seen) {
Seen = Seen || new WeakSet(); Seen = Seen || new WeakSet();
function Try(V) { function Try(V) {
if (!IsSceneNode(V) || Seen.has(V)) return; if (!IsSceneNode(V) || Seen.has(V)) return;
Seen.add(V); CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
CollectMeshNodes(V, nodes, 0, 'scan', stats);
} }
try { try {
tank.qz1_1.s2a_1.forEach(function (VL) { tank.qz1_1.s2a_1.forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL; var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!comp) return; if (!IsPlainObj(comp)) return;
for (var K in comp) { for (var K in comp) {
var V = comp[K]; var V = comp[K];
if (!V || typeof V !== 'object') continue; if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; } if (IsSceneNode(V)) { Try(V); 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 (!V2 || typeof V2 !== 'object') continue; if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; } if (IsSceneNode(V2)) { Try(V2); continue; }
for (var K3 in V2) { for (var K3 in V2) {
var V3 = V2[K3]; var V3 = V2[K3];
if (V3 && typeof V3 === 'object') Try(V3); if (IsPlainObj(V3)) Try(V3);
} }
} }
} catch (e) {} } catch (e) {}
@@ -2072,21 +2085,30 @@
// ===== 提取坦克的mesh数据 ===== // ===== 提取坦克的mesh数据 =====
function GetTankMeshData(tank, stats) { function GetTankMeshData(tank, stats) {
var nodes = []; var nodes = [];
var skin = GetHullSkin(tank, stats); var Seen = new WeakSet();
var skinComp = GetSkinComponent(tank);
var skin = (skinComp && skinComp.g159_1 && skinComp.g159_1.g17r_1) ? skinComp.g159_1 : null;
if (stats) {
stats.hullSource = skin ? 'g159_1' : (skinComp ? '有组件但无皮肤' : '无皮肤组件');
stats.hullParts = (skin && typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
}
// ① 车身根节点:皮肤组件 h159_1 是「所有部件节点」的总根(比逐个部件更完整)
if (skinComp && skinComp.h159_1) {
try { CollectMeshNodes(skinComp.h159_1, nodes, 0, 'hull', stats ? stats.hull : null, Seen); } catch (e) {}
}
// ② 部件表:某些构建下根节点不完整,仍逐个部件补一遍(Seen 自动去重)
if (skin) { if (skin) {
try { try {
if (stats) stats.hullParts = (typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
ForEachInList(skin.g17r_1, function (part) { ForEachInList(skin.g17r_1, function (part) {
if (!part) return; if (!part) return;
// 新版:part.j17r_1 是单个 BP 节点;旧版是节点列表 var j = part.j17r_1; // 新版:单个 BP 节点;旧版:节点列表
var j = part.j17r_1;
if (!j) return; if (!j) return;
if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') { if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') {
ForEachInList(j, function (bp) { ForEachInList(j, function (bp) {
if (bp) CollectMeshNodes(bp, nodes, 0, 'hull', stats ? stats.hull : null); if (bp) CollectMeshNodes(bp, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
}); });
} else { } else {
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null); CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
} }
}); });
} catch(e) {} } catch(e) {}
@@ -2095,15 +2117,11 @@
if (stats) stats.turretComp = !!turretNode; if (stats) stats.turretComp = !!turretNode;
if (turretNode) { if (turretNode) {
if (stats) stats.turretRoot = true; if (stats) stats.turretRoot = true;
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null); } catch(e) {} try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null, Seen); } catch(e) {}
}
// 结构路径没拿到任何 mesh(皮肤/炮塔节点缺失或结构不符)时,直接扫实体所有组件字段找 3D 节点
if (!skin || !turretNode || nodes.length === 0) {
var Seen = new WeakSet();
for (var ni = 0; ni < nodes.length; ni++) { if (nodes[ni] && nodes[ni].node) Seen.add(nodes[ni].node); }
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
if (stats) stats.hullSource = (stats.hullSource || '扫描');
} }
// ③ 无条件结构扫描兜底:把组件字段里能看到的 3D 节点全部补进来
// (解决「有的坦克只有点」「有的只有头没有身子」——结构字段不完整时漏采)
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
return nodes; return nodes;
} }
+41 -23
View File
@@ -458,8 +458,13 @@
} }
// ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) ===== // ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group, stats) { // Seen:跨「车身根 / 部件表 / 炮塔 / 扫描」多路径去重,避免同一节点被画多次
if (!bpNode || depth > 5) return; function CollectMeshNodes(bpNode, nodes, depth, group, stats, Seen) {
if (!bpNode || depth > 12) return;
if (Seen) {
if (Seen.has(bpNode)) return;
Seen.add(bpNode);
}
if (stats) stats.seen++; if (stats) stats.seen++;
var name = bpNode.g35_1 || ''; var name = bpNode.g35_1 || '';
if (bpNode.v3m_1) { if (bpNode.v3m_1) {
@@ -498,7 +503,7 @@
((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null); ((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null);
if (Children) { if (Children) {
ForEachInList(Children, function (child) { ForEachInList(Children, function (child) {
if (child) CollectMeshNodes(child, nodes, depth + 1, group, stats); if (child) CollectMeshNodes(child, nodes, depth + 1, group, stats, Seen);
}); });
} }
} }
@@ -512,30 +517,38 @@
V.r35_1 && typeof V.r35_1 === 'object'); V.r35_1 && typeof V.r35_1 === 'object');
} }
// 只遍历普通对象:TypedArray / 数组 / Map / Set 的 for-in 会产出成千上万个键,必须跳过
function IsPlainObj(V) {
if (!V || typeof V !== 'object') return false;
if (Array.isArray(V) || ArrayBuffer.isView(V)) return false;
if (typeof Map !== 'undefined' && V instanceof Map) return false;
if (typeof Set !== 'undefined' && V instanceof Set) return false;
return true;
}
function ScanMeshNodes(tank, nodes, stats, Seen) { function ScanMeshNodes(tank, nodes, stats, Seen) {
Seen = Seen || new WeakSet(); Seen = Seen || new WeakSet();
function Try(V) { function Try(V) {
if (!IsSceneNode(V) || Seen.has(V)) return; if (!IsSceneNode(V) || Seen.has(V)) return;
Seen.add(V); CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
CollectMeshNodes(V, nodes, 0, 'scan', stats);
} }
try { try {
tank.qz1_1.s2a_1.forEach(function (VL) { tank.qz1_1.s2a_1.forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL; var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!comp) return; if (!IsPlainObj(comp)) return;
for (var K in comp) { for (var K in comp) {
var V = comp[K]; var V = comp[K];
if (!V || typeof V !== 'object') continue; if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; } if (IsSceneNode(V)) { Try(V); 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 (!V2 || typeof V2 !== 'object') continue; if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; } if (IsSceneNode(V2)) { Try(V2); continue; }
for (var K3 in V2) { for (var K3 in V2) {
var V3 = V2[K3]; var V3 = V2[K3];
if (V3 && typeof V3 === 'object') Try(V3); if (IsPlainObj(V3)) Try(V3);
} }
} }
} catch (e) {} } catch (e) {}
@@ -547,21 +560,30 @@
// ===== 提取坦克的mesh数据 ===== // ===== 提取坦克的mesh数据 =====
function GetTankMeshData(tank, stats) { function GetTankMeshData(tank, stats) {
var nodes = []; var nodes = [];
var skin = GetHullSkin(tank, stats); var Seen = new WeakSet();
var skinComp = GetSkinComponent(tank);
var skin = (skinComp && skinComp.g159_1 && skinComp.g159_1.g17r_1) ? skinComp.g159_1 : null;
if (stats) {
stats.hullSource = skin ? 'g159_1' : (skinComp ? '有组件但无皮肤' : '无皮肤组件');
stats.hullParts = (skin && typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
}
// ① 车身根节点:皮肤组件 h159_1 是「所有部件节点」的总根(比逐个部件更完整)
if (skinComp && skinComp.h159_1) {
try { CollectMeshNodes(skinComp.h159_1, nodes, 0, 'hull', stats ? stats.hull : null, Seen); } catch (e) {}
}
// ② 部件表:某些构建下根节点不完整,仍逐个部件补一遍(Seen 自动去重)
if (skin) { if (skin) {
try { try {
if (stats) stats.hullParts = (typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
ForEachInList(skin.g17r_1, function (part) { ForEachInList(skin.g17r_1, function (part) {
if (!part) return; if (!part) return;
// 新版:part.j17r_1 是单个 BP 节点;旧版是节点列表 var j = part.j17r_1; // 新版:单个 BP 节点;旧版:节点列表
var j = part.j17r_1;
if (!j) return; if (!j) return;
if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') { if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') {
ForEachInList(j, function (bp) { ForEachInList(j, function (bp) {
if (bp) CollectMeshNodes(bp, nodes, 0, 'hull', stats ? stats.hull : null); if (bp) CollectMeshNodes(bp, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
}); });
} else { } else {
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null); CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
} }
}); });
} catch(e) {} } catch(e) {}
@@ -570,15 +592,11 @@
if (stats) stats.turretComp = !!turretNode; if (stats) stats.turretComp = !!turretNode;
if (turretNode) { if (turretNode) {
if (stats) stats.turretRoot = true; if (stats) stats.turretRoot = true;
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null); } catch(e) {} try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null, Seen); } catch(e) {}
}
// 结构路径没拿到任何 mesh(皮肤/炮塔节点缺失或结构不符)时,直接扫实体所有组件字段找 3D 节点
if (!skin || !turretNode || nodes.length === 0) {
var Seen = new WeakSet();
for (var ni = 0; ni < nodes.length; ni++) { if (nodes[ni] && nodes[ni].node) Seen.add(nodes[ni].node); }
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
if (stats) stats.hullSource = (stats.hullSource || '扫描');
} }
// ③ 无条件结构扫描兜底:把组件字段里能看到的 3D 节点全部补进来
// (解决「有的坦克只有点」「有的只有头没有身子」——结构字段不完整时漏采)
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
return nodes; return nodes;
} }