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 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group, stats) {
if (!bpNode || depth > 5) return;
// Seen:跨「车身根 / 部件表 / 炮塔 / 扫描」多路径去重,避免同一节点被画多次
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++;
var name = bpNode.g35_1 || '';
if (bpNode.v3m_1) {
@@ -2023,7 +2028,7 @@
((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null);
if (Children) {
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');
}
// 只遍历普通对象: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) {
Seen = Seen || new WeakSet();
function Try(V) {
if (!IsSceneNode(V) || Seen.has(V)) return;
Seen.add(V);
CollectMeshNodes(V, nodes, 0, 'scan', stats);
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!comp) return;
if (!IsPlainObj(comp)) return;
for (var K in comp) {
var V = comp[K];
if (!V || typeof V !== 'object') continue;
if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; }
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
try {
for (var K2 in V) {
var V2 = V[K2];
if (!V2 || typeof V2 !== 'object') continue;
if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; }
for (var K3 in V2) {
var V3 = V2[K3];
if (V3 && typeof V3 === 'object') Try(V3);
if (IsPlainObj(V3)) Try(V3);
}
}
} catch (e) {}
@@ -2072,21 +2085,30 @@
// ===== 提取坦克的mesh数据 =====
function GetTankMeshData(tank, stats) {
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) {
try {
if (stats) stats.hullParts = (typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
ForEachInList(skin.g17r_1, function (part) {
if (!part) return;
// 新版:part.j17r_1 是单个 BP 节点;旧版是节点列表
var j = part.j17r_1;
var j = part.j17r_1; // 新版:单个 BP 节点;旧版:节点列表
if (!j) return;
if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') {
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 {
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null);
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
}
});
} catch(e) {}
@@ -2095,15 +2117,11 @@
if (stats) stats.turretComp = !!turretNode;
if (turretNode) {
if (stats) stats.turretRoot = true;
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null); } 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 || '扫描');
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null, Seen); } catch(e) {}
}
// ③ 无条件结构扫描兜底:把组件字段里能看到的 3D 节点全部补进来
// (解决「有的坦克只有点」「有的只有头没有身子」——结构字段不完整时漏采)
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
return nodes;
}
+41 -23
View File
@@ -1983,8 +1983,13 @@
}
// ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group, stats) {
if (!bpNode || depth > 5) return;
// Seen:跨「车身根 / 部件表 / 炮塔 / 扫描」多路径去重,避免同一节点被画多次
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++;
var name = bpNode.g35_1 || '';
if (bpNode.v3m_1) {
@@ -2023,7 +2028,7 @@
((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null);
if (Children) {
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');
}
// 只遍历普通对象: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) {
Seen = Seen || new WeakSet();
function Try(V) {
if (!IsSceneNode(V) || Seen.has(V)) return;
Seen.add(V);
CollectMeshNodes(V, nodes, 0, 'scan', stats);
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!comp) return;
if (!IsPlainObj(comp)) return;
for (var K in comp) {
var V = comp[K];
if (!V || typeof V !== 'object') continue;
if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; }
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
try {
for (var K2 in V) {
var V2 = V[K2];
if (!V2 || typeof V2 !== 'object') continue;
if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; }
for (var K3 in V2) {
var V3 = V2[K3];
if (V3 && typeof V3 === 'object') Try(V3);
if (IsPlainObj(V3)) Try(V3);
}
}
} catch (e) {}
@@ -2072,21 +2085,30 @@
// ===== 提取坦克的mesh数据 =====
function GetTankMeshData(tank, stats) {
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) {
try {
if (stats) stats.hullParts = (typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
ForEachInList(skin.g17r_1, function (part) {
if (!part) return;
// 新版:part.j17r_1 是单个 BP 节点;旧版是节点列表
var j = part.j17r_1;
var j = part.j17r_1; // 新版:单个 BP 节点;旧版:节点列表
if (!j) return;
if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') {
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 {
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null);
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
}
});
} catch(e) {}
@@ -2095,15 +2117,11 @@
if (stats) stats.turretComp = !!turretNode;
if (turretNode) {
if (stats) stats.turretRoot = true;
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null); } 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 || '扫描');
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null, Seen); } catch(e) {}
}
// ③ 无条件结构扫描兜底:把组件字段里能看到的 3D 节点全部补进来
// (解决「有的坦克只有点」「有的只有头没有身子」——结构字段不完整时漏采)
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
return nodes;
}
+41 -23
View File
@@ -458,8 +458,13 @@
}
// ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group, stats) {
if (!bpNode || depth > 5) return;
// Seen:跨「车身根 / 部件表 / 炮塔 / 扫描」多路径去重,避免同一节点被画多次
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++;
var name = bpNode.g35_1 || '';
if (bpNode.v3m_1) {
@@ -498,7 +503,7 @@
((bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') ? bpNode.p35_1 : null);
if (Children) {
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');
}
// 只遍历普通对象: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) {
Seen = Seen || new WeakSet();
function Try(V) {
if (!IsSceneNode(V) || Seen.has(V)) return;
Seen.add(V);
CollectMeshNodes(V, nodes, 0, 'scan', stats);
CollectMeshNodes(V, nodes, 0, 'scan', stats, Seen);
}
try {
tank.qz1_1.s2a_1.forEach(function (VL) {
var comp = (VL && typeof VL.g1 === 'function') ? VL.g1(0) : VL;
if (!comp) return;
if (!IsPlainObj(comp)) return;
for (var K in comp) {
var V = comp[K];
if (!V || typeof V !== 'object') continue;
if (!IsPlainObj(V)) continue;
if (IsSceneNode(V)) { Try(V); continue; }
// 深度 2/3:节点常包在皮肤对象/包装对象里(如 skin.h159_1、weaponSkin.e11p_1.a17q_1)
try {
for (var K2 in V) {
var V2 = V[K2];
if (!V2 || typeof V2 !== 'object') continue;
if (!IsPlainObj(V2)) continue;
if (IsSceneNode(V2)) { Try(V2); continue; }
for (var K3 in V2) {
var V3 = V2[K3];
if (V3 && typeof V3 === 'object') Try(V3);
if (IsPlainObj(V3)) Try(V3);
}
}
} catch (e) {}
@@ -547,21 +560,30 @@
// ===== 提取坦克的mesh数据 =====
function GetTankMeshData(tank, stats) {
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) {
try {
if (stats) stats.hullParts = (typeof skin.g17r_1.e1 === 'function') ? skin.g17r_1.e1() : -1;
ForEachInList(skin.g17r_1, function (part) {
if (!part) return;
// 新版:part.j17r_1 是单个 BP 节点;旧版是节点列表
var j = part.j17r_1;
var j = part.j17r_1; // 新版:单个 BP 节点;旧版:节点列表
if (!j) return;
if (typeof j === 'object' && typeof j.e1 === 'function' && typeof j.g1 === 'function') {
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 {
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null);
CollectMeshNodes(j, nodes, 0, 'hull', stats ? stats.hull : null, Seen);
}
});
} catch(e) {}
@@ -570,15 +592,11 @@
if (stats) stats.turretComp = !!turretNode;
if (turretNode) {
if (stats) stats.turretRoot = true;
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null); } 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 || '扫描');
try { CollectMeshNodes(turretNode, nodes, 0, 'turret', stats ? stats.turret : null, Seen); } catch(e) {}
}
// ③ 无条件结构扫描兜底:把组件字段里能看到的 3D 节点全部补进来
// (解决「有的坦克只有点」「有的只有头没有身子」——结构字段不完整时漏采)
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
return nodes;
}