fix(wireframe): 去重键去掉世界坐标并取消扫描节流,修复模型闪烁

This commit is contained in:
TermiNexus committed 2026-10-19 22:37:31 +08:00
1 parent a83f2a5407
commit 41b94ef0f9
3 files changed
+21 -60

No files matched your search

@@ -2082,9 +2082,9 @@
} catch (e) {} } catch (e) {}
} }
// 结果级去重:同一 mesh 资源 + 同一世界平移,只保留一份 // 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
// (引擎里同一逻辑节点可能存在「皮肤树 / 场景树」两套实例,AABB/矩阵更新时机不同, // 注意:**不要把世界坐标写进键**——坦克移动时两套实例的平移会差一帧,
// 两套都画会出现「复制出一个不动的身体,过一会才跟上」) // 键随位置变化会导致「这帧去重、下帧不去重」→ 模型闪烁。
var TpIds = new WeakMap(); var TpIds = new WeakMap();
var TpNextId = 1; var TpNextId = 1;
function DedupNodes(nodes) { function DedupNodes(nodes) {
@@ -2096,9 +2096,7 @@
var Tp = N.tp; var Tp = N.tp;
var Id = TpIds.get(Tp); var Id = TpIds.get(Tp);
if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); } if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); }
var M = N.node && N.node.p35_1; var Key = Id + ':' + N.name;
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; if (Keys.has(Key)) continue;
Keys.add(Key); Keys.add(Key);
Out.push(N); Out.push(N);
@@ -2106,20 +2104,9 @@
return Out; 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数据 ===== // ===== 提取坦克的mesh数据 =====
// 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体") // 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体")
// ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(节流兜底) // ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(兜底)
function GetTankMeshData(tank, stats) { function GetTankMeshData(tank, stats) {
var nodes = []; var nodes = [];
var Seen = new WeakSet(); var Seen = new WeakSet();
@@ -2150,8 +2137,8 @@
}); });
} catch(e) {} } catch(e) {}
} }
// ③ 仍然为空才做组件字段扫描(节流,避免每帧全量遍历造成卡顿) // ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
if (nodes.length === 0 && ShouldScanNow(tank)) { if (nodes.length === 0) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {} try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
} }
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉) // ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
+7 -20
View File
@@ -2082,9 +2082,9 @@
} catch (e) {} } catch (e) {}
} }
// 结果级去重:同一 mesh 资源 + 同一世界平移,只保留一份 // 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
// (引擎里同一逻辑节点可能存在「皮肤树 / 场景树」两套实例,AABB/矩阵更新时机不同, // 注意:**不要把世界坐标写进键**——坦克移动时两套实例的平移会差一帧,
// 两套都画会出现「复制出一个不动的身体,过一会才跟上」) // 键随位置变化会导致「这帧去重、下帧不去重」→ 模型闪烁。
var TpIds = new WeakMap(); var TpIds = new WeakMap();
var TpNextId = 1; var TpNextId = 1;
function DedupNodes(nodes) { function DedupNodes(nodes) {
@@ -2096,9 +2096,7 @@
var Tp = N.tp; var Tp = N.tp;
var Id = TpIds.get(Tp); var Id = TpIds.get(Tp);
if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); } if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); }
var M = N.node && N.node.p35_1; var Key = Id + ':' + N.name;
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; if (Keys.has(Key)) continue;
Keys.add(Key); Keys.add(Key);
Out.push(N); Out.push(N);
@@ -2106,20 +2104,9 @@
return Out; 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数据 ===== // ===== 提取坦克的mesh数据 =====
// 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体") // 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体")
// ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(节流兜底) // ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(兜底)
function GetTankMeshData(tank, stats) { function GetTankMeshData(tank, stats) {
var nodes = []; var nodes = [];
var Seen = new WeakSet(); var Seen = new WeakSet();
@@ -2150,8 +2137,8 @@
}); });
} catch(e) {} } catch(e) {}
} }
// ③ 仍然为空才做组件字段扫描(节流,避免每帧全量遍历造成卡顿) // ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
if (nodes.length === 0 && ShouldScanNow(tank)) { if (nodes.length === 0) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {} try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
} }
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉) // ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)
+7 -20
View File
@@ -557,9 +557,9 @@
} catch (e) {} } catch (e) {}
} }
// 结果级去重:同一 mesh 资源 + 同一世界平移,只保留一份 // 结果级去重:同一 mesh 资源 + 同一节点名,只保留一份
// (引擎里同一逻辑节点可能存在「皮肤树 / 场景树」两套实例,AABB/矩阵更新时机不同, // 注意:**不要把世界坐标写进键**——坦克移动时两套实例的平移会差一帧,
// 两套都画会出现「复制出一个不动的身体,过一会才跟上」) // 键随位置变化会导致「这帧去重、下帧不去重」→ 模型闪烁。
var TpIds = new WeakMap(); var TpIds = new WeakMap();
var TpNextId = 1; var TpNextId = 1;
function DedupNodes(nodes) { function DedupNodes(nodes) {
@@ -571,9 +571,7 @@
var Tp = N.tp; var Tp = N.tp;
var Id = TpIds.get(Tp); var Id = TpIds.get(Tp);
if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); } if (!Id) { Id = TpNextId++; TpIds.set(Tp, Id); }
var M = N.node && N.node.p35_1; var Key = Id + ':' + N.name;
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; if (Keys.has(Key)) continue;
Keys.add(Key); Keys.add(Key);
Out.push(N); Out.push(N);
@@ -581,20 +579,9 @@
return Out; 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数据 ===== // ===== 提取坦克的mesh数据 =====
// 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体") // 采集策略:**单一路径优先**,绝不把两条路径的结果混在一起(混在一起就是"复制身体")
// ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(节流兜底) // ① 皮肤组件 h159_1(实际渲染树根)→ ② 部件表 → ③ 组件字段扫描(兜底)
function GetTankMeshData(tank, stats) { function GetTankMeshData(tank, stats) {
var nodes = []; var nodes = [];
var Seen = new WeakSet(); var Seen = new WeakSet();
@@ -625,8 +612,8 @@
}); });
} catch(e) {} } catch(e) {}
} }
// ③ 仍然为空才做组件字段扫描(节流,避免每帧全量遍历造成卡顿) // ③ 仍然为空才做组件字段扫描(不加节流:节流会导致「这帧有模型、下帧只有点」的闪烁)
if (nodes.length === 0 && ShouldScanNow(tank)) { if (nodes.length === 0) {
try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {} try { ScanMeshNodes(tank, nodes, stats ? stats.scan : null, Seen); } catch (e) {}
} }
// ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉) // ④ 炮塔(独立于车身根;重复实例由 DedupNodes 收掉)