fix(wireframe): 采集改为单路径优先并按 mesh+位置去重,修复重复身体与卡顿

This commit is contained in:
TermiNexus committed 2026-10-19 22:32:10 +08:00
1 parent af8cde08d2
commit 797f28c8cd
3 files changed
+138 -21

No files matched your search

@@ -2082,7 +2082,44 @@
} catch (e) {}
}
// 结果级去重:同一 mesh 资源 + 同一世界平移,只保留一份
// (引擎里同一逻辑节点可能存在「皮肤树 / 场景树」两套实例,AABB/矩阵更新时机不同,
// 两套都画会出现「复制出一个不动的身体,过一会才跟上」)
var TpIds = new WeakMap();
var TpNextId = 1;
function DedupNodes(nodes) {
if (nodes.length < 2) return nodes;
var Keys = new Set();
var Out = [];
for (var i = 0; i < nodes.length; i++) {
var N = nodes[i];
var Tp = N.tp;
var Id = TpIds.get(Tp);
if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); }
var M = N.node && N.node.p35_1;
var Pos = M ? (Math.round(M[12]) + ',' + Math.round(M[13]) + ',' + Math.round(M[14])) : '?';
var Key = Id + ':' + N.name + ':' + Pos;
if (Keys.has(Key)) continue;
Keys.add(Key);
Out.push(N);
}
return Out;
}
// 扫描节流:仅在主路径完全采不到时使用,且同一辆坦克至少间隔 10 帧
var ScanTick = 0;
var LastScanAt = new WeakMap();
function ShouldScanNow(tank) {
ScanTick++;
var Last = LastScanAt.get(tank) || 0;
if (ScanTick - Last < 10) return false;
LastScanAt.set(tank, ScanTick);
return true;
}
// ===== 提取坦克的mesh数据 =====
// 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体")
// ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(节流兜底)
function GetTankMeshData(tank, stats) {
var nodes = [];
var Seen = new WeakSet();
@@ -2092,12 +2129,12 @@
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 (nodes.length === 0 && skin) {
try {
ForEachInList(skin.g17r_1, function (part) {
if (!part) return;
@@ -2113,16 +2150,18 @@
});
} catch(e) {}
}
// ③ 仍然为空才做组件字段扫描(节流,避免每帧全量遍历造成卡顿)
if (nodes.length === 0 && ShouldScanNow(tank)) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
}
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
var turretNode = GetTurretNode(tank);
if (stats) stats.turretComp = !!turretNode;
if (turretNode) {
if (stats) stats.turretRoot = true;
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;
return DedupNodes(nodes);
}
// ===== 残留节点检测(世界矩阵"冻结")→ 拖拽跟随 =====
+46 -7
View File
@@ -2082,7 +2082,44 @@
} catch (e) {}
}
// 结果级去重:同一 mesh 资源 + 同一世界平移,只保留一份
// (引擎里同一逻辑节点可能存在「皮肤树 / 场景树」两套实例,AABB/矩阵更新时机不同,
// 两套都画会出现「复制出一个不动的身体,过一会才跟上」)
var TpIds = new WeakMap();
var TpNextId = 1;
function DedupNodes(nodes) {
if (nodes.length < 2) return nodes;
var Keys = new Set();
var Out = [];
for (var i = 0; i < nodes.length; i++) {
var N = nodes[i];
var Tp = N.tp;
var Id = TpIds.get(Tp);
if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); }
var M = N.node && N.node.p35_1;
var Pos = M ? (Math.round(M[12]) + ',' + Math.round(M[13]) + ',' + Math.round(M[14])) : '?';
var Key = Id + ':' + N.name + ':' + Pos;
if (Keys.has(Key)) continue;
Keys.add(Key);
Out.push(N);
}
return Out;
}
// 扫描节流:仅在主路径完全采不到时使用,且同一辆坦克至少间隔 10 帧
var ScanTick = 0;
var LastScanAt = new WeakMap();
function ShouldScanNow(tank) {
ScanTick++;
var Last = LastScanAt.get(tank) || 0;
if (ScanTick - Last < 10) return false;
LastScanAt.set(tank, ScanTick);
return true;
}
// ===== 提取坦克的mesh数据 =====
// 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体")
// ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(节流兜底)
function GetTankMeshData(tank, stats) {
var nodes = [];
var Seen = new WeakSet();
@@ -2092,12 +2129,12 @@
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 (nodes.length === 0 && skin) {
try {
ForEachInList(skin.g17r_1, function (part) {
if (!part) return;
@@ -2113,16 +2150,18 @@
});
} catch(e) {}
}
// ③ 仍然为空才做组件字段扫描(节流,避免每帧全量遍历造成卡顿)
if (nodes.length === 0 && ShouldScanNow(tank)) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
}
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
var turretNode = GetTurretNode(tank);
if (stats) stats.turretComp = !!turretNode;
if (turretNode) {
if (stats) stats.turretRoot = true;
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;
return DedupNodes(nodes);
}
// ===== 残留节点检测(世界矩阵"冻结")→ 拖拽跟随 =====
+46 -7
View File
@@ -557,7 +557,44 @@
} catch (e) {}
}
// 结果级去重:同一 mesh 资源 + 同一世界平移,只保留一份
// (引擎里同一逻辑节点可能存在「皮肤树 / 场景树」两套实例,AABB/矩阵更新时机不同,
// 两套都画会出现「复制出一个不动的身体,过一会才跟上」)
var TpIds = new WeakMap();
var TpNextId = 1;
function DedupNodes(nodes) {
if (nodes.length < 2) return nodes;
var Keys = new Set();
var Out = [];
for (var i = 0; i < nodes.length; i++) {
var N = nodes[i];
var Tp = N.tp;
var Id = TpIds.get(Tp);
if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); }
var M = N.node && N.node.p35_1;
var Pos = M ? (Math.round(M[12]) + ',' + Math.round(M[13]) + ',' + Math.round(M[14])) : '?';
var Key = Id + ':' + N.name + ':' + Pos;
if (Keys.has(Key)) continue;
Keys.add(Key);
Out.push(N);
}
return Out;
}
// 扫描节流:仅在主路径完全采不到时使用,且同一辆坦克至少间隔 10 帧
var ScanTick = 0;
var LastScanAt = new WeakMap();
function ShouldScanNow(tank) {
ScanTick++;
var Last = LastScanAt.get(tank) || 0;
if (ScanTick - Last < 10) return false;
LastScanAt.set(tank, ScanTick);
return true;
}
// ===== 提取坦克的mesh数据 =====
// 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体")
// ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(节流兜底)
function GetTankMeshData(tank, stats) {
var nodes = [];
var Seen = new WeakSet();
@@ -567,12 +604,12 @@
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 (nodes.length === 0 && skin) {
try {
ForEachInList(skin.g17r_1, function (part) {
if (!part) return;
@@ -588,16 +625,18 @@
});
} catch(e) {}
}
// ③ 仍然为空才做组件字段扫描(节流,避免每帧全量遍历造成卡顿)
if (nodes.length === 0 && ShouldScanNow(tank)) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
}
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
var turretNode = GetTurretNode(tank);
if (stats) stats.turretComp = !!turretNode;
if (turretNode) {
if (stats) stats.turretRoot = true;
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;
return DedupNodes(nodes);
}
// ===== 残留节点检测(世界矩阵"冻结")→ 拖拽跟随 =====