three-base 2.0.193 → 2.0.195

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,6 @@ import { type PanoStitcher } from "./panoTileStitcher";
4
4
  import type { MktMeshData, MktTextureInfo, WallPoint, WallSide } from "./types";
5
5
  import { ImagePlane, type ImagePlaneData } from "./imagePlane";
6
6
  import { ModelSlot, type ModelSlotData } from "./modelSlot";
7
- import { GlassPane, type GlassPaneData } from "./glassPane";
8
7
  export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
9
8
  /** 纹理列表(每个纹理的 url + 世界矩阵) */
10
9
  textures: MktTextureInfo[];
@@ -129,23 +128,6 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
129
128
  imagePlanes: ImagePlane[];
130
129
  /** 模型槽层(同一棵子树,口径同上) */
131
130
  models: ModelSlot[];
132
- /**
133
- * 玻璃面层(⭐ 2026-10-28 需求「正方的面」):同一棵子树,口径与上面两层一致。
134
- * 与那两层的区别只在材质(MeshPhysicalMaterial + transmission + DoubleSide,见 `./glassPane.ts`)
135
- * ⇒ 它**没有自己的内容纹理**,反射源共用下面那一张 `_glassEnvTex`。
136
- */
137
- glassPanes: GlassPane[];
138
- /**
139
- * 玻璃面反射环境的**源地址** = 第一个全景的整图地址(`textures[0].url`),空串 = 还没有全景可用。
140
- * ⚠️ 这里存的是**未拼尺寸参数的原始地址**(与 `_texCache` 的键同一口径)⇒ 全景换了地址才发现要重载;
141
- * 下载时按当前 `_ossSize` 档过 `resolveOssUrl`(默认为 1024 那一档)。PMREM 卷积输出的分辨率上限远小于 1024
142
- * ⇒ 反射精度由那一步决定,用缩放副本不降低观感,换来的是省掉整张原图(实测 11904×5952 ≈ 283MB)的解码内存。
143
- * ⚠️ 为什么不用「第一个全景那两张拼合槽 RT」:每张 RT 装的是原图**左/右半幅**且压成 1:1 方图,
144
- * 当等距柱状环境采会横向拉伸一倍 + 缺半边;且它的 mapping 是 UVMapping,three 不会替它做 PMREM ⇒ 采出花屏。
145
- */
146
- private _glassEnvKey;
147
- /** 已就绪的反射环境纹理(null = 未就绪 / 没有全景);同一张共用给所有玻璃面 ⇒ 只下载、只卷积一次 */
148
- private _glassEnvTex;
149
131
  /**
150
132
  * 那 4 张瓦片的下载/解码缓存。⭐ 与全景 `_texCache` 同一条旨(10-02「纹理都缓存在对象里面,执行 dispose 时才销毁」)
151
133
  * ⇒ 切工程**不**清它,回来那一圈直接命中缓存、不会再空一格;只随本对象 `dispose()` 释放。
@@ -392,32 +374,6 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
392
374
  private _queueModelMount;
393
375
  /** 全局队列里的那一帧:只挂这一槽,挂好才回调(`onEachLoad` = 按需渲染宿主的标脏钩子) */
394
376
  private _mountModelStep;
395
- /** 单颗玻璃面入层(编辑态双击落点用):进数组 + 挂进本对象子树 + 先吃当前反射源,再确认源在下路没有 */
396
- addGlassPane(gp: GlassPane): GlassPane;
397
- /** 摘掉一颗并释放它的几何/材质;反射纹理是共用那张 ⇒ 最后一颗也摘掉时才由 `syncGlassEnvironment` 收(按源地址记账,⛔ 不在这里直接 dispose) */
398
- removeGlassPane(gp: GlassPane): void;
399
- /**
400
- * 整层重建玻璃面(`importData` 与预览那一路)。玻璃面不引用任何素材文件 ⇒ ⛔ 不过 `absAssetUrl`
401
- * (地址只有反射源那一处,而它取的是**本对象内存里**的 `textures[0].url`,已经是补过域名的形态)。
402
- */
403
- setGlassPanes(datas: GlassPaneData[]): void;
404
- /**
405
- * 把「第一个全景」的整图当成玻璃面的反射环境(2026-10-28 需求)。
406
- *
407
- * 判据 = 源地址变了没有(⛔ 不按下标记:删掉 0 号景点后原 1 号自动顶上来,那时地址会变 ⇒ 自愈)。
408
- * 调用点只有四处,⛔ 不进 `onBeforeRender` 自驱:① 单颗入层 ② 单颗摘除 ③ 整层重建 ④ 编辑态每帧 tick 里与
409
- * `feedImagePlaneTiles()` 并排那一句(源地址没变就一句比较直接返回 ⇒ 无玻璃面的场景零成本,
410
- * 而全景被增删/换图那些路都不必各自记得再来点一次)。
411
- *
412
- * ⚠️ 换源那一步必须**先摘后放**:材质还指着这张、位图却已 `close()` ⇒ 采出来是空图。
413
- * ⚠️ 下载走 `loadPanoTexture`(解码在工作线程)并自动向 `DefaultLoadingManager` 记账 ⇒ 预览弹窗那个
414
- * 加载进度条能看见它,与全景瓦片同一本账。
415
- * ⚠️ 结果只存进 `_glassEnvTex`,⛔ 不进 `_texCache`:那张表在 `setOssSize` 换档时会被整批 dispose
416
- * (`_reloadActiveTexture`),而正在被材质引用的这张不能被那样顺手清掉。
417
- */
418
- syncGlassEnvironment(): void;
419
- /** 把同一张反射源发给每一颗玻璃面(`GlassPane` 内部按对象判等 ⇒ 重复发同一张不会重编译)。 */
420
- private _applyGlassEnv;
421
377
  /**
422
378
  * 把各颗平面那 4 张瓦片的纹理绑进材质(10-22 晚定的口径:不走拼合器 RT)。
423
379
  * 2026-10-26 决定改成**逐帧**:一次把整层地址全丢给下载器(下载/解码在网络与工作线程侧,不占这一帧),
@@ -450,34 +406,37 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
450
406
  * @param onModelLoad 每只 GLB 落地时回调一次(可选)——按需渲染的宿主(编辑器)拿它标脏;每帧真画的预览不传 */
451
407
  importData(data: MktMeshData, projName?: string, onModelLoad?: () => void): void;
452
408
  /**
453
- * 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**的素材点名抢在别的之前加载(2026-10-29 需求)。
409
+ * 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**按存档顺序登记进拼合器的**加载任务队列**
410
+ *(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
411
+ * 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
412
+ * 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
413
+ * 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」)。
454
414
  * 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
455
415
  *(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
456
416
  *
457
- * 为什么能「先加载」:拼合器每帧那三道工序(补下载 → 画已进 GPU 的 → 再传一张)都按
458
- * `_groups` 这张 Map 的**在册顺序**取料,而下载额度只有「每帧 2 张 / 在途 ≤8 / 已解码待上传 ≤8」
459
- * (`panoTileStitcher.ts` 顶部那三个常数)⇒ 排在头部的两组把额度吃满,其余全景要等它们画满才轮得到。
460
- * 平时这个顺序由每帧的槽位名册定(离注视点近的先下),本方法把它钉成「存档数组的前两个」,与机位无关。
461
- * ⚠️⚠️ 为什么点名时要带第三个参 `front = true`(每帧把这两批**挪回队首**)而⛔ 不只是「先入册」:
462
- * 在册顺序是拼合器自己的状态 —— 后入册的只会**追加到队尾**,而 `ensure` 幂等、已在册就不再移动。
463
- * 预览这条路恰好是最坏的那种排法:`feedFaceMapping` 开头还有一记 `pump`,拼合器**第一次**拿到渲染器时
464
- * 必走 `_switchRenderer`(`null → 真 renderer` 也算换,弹窗每开一次就是换一次)把在册组整份作废,
465
- * 随后名册按距离重新入册占了队首 ⇒ 本方法下一帧重新入册只能排在它们后面,而且**从此再也翻不了身**。
466
- * 2026-10-29 逐帧打点就是这个形状:帧 1 `p0,p1,p11…p4` → 帧 2 起 `p11…p4,p0,p1`,
467
- * 40 帧下来 p0 只下到 1 片、p1 下到 30 片,离注视点最近那颗第 13 条请求就开始了 = 线上报的现象。
468
- * ⇒ 带 `front` 之后顺序每帧重新成立,与谁先入册、作废过几次、机位在哪都无关
469
- * (真跑自证在护栏 BE ④:`D` 后入册排队尾,带 `front` 再点一次 ⇒ 立刻回到队首、下一帧下载全归它)。
470
- * ⚠️ 为什么是**每帧**而不是「载入那一次点一记」:上面那次作废就发生在载入之后的第一帧,
471
- * 只在 `importData` 里点名等于白点;本方法点在 `pump` 之后、名册逐槽 `ensure` 之前,且每帧都点
472
- * (已在册且已在队首 ⇒ `_toFront` 里一次 `keys().next()` 就返回,没有额外开销)。
473
- * ⛔ 不另立第二把下载尺:这里用的就是 `feedFaceMapping` 每帧在用的同一个 `ensure(info.tiles, …)`
474
- * 与同一个批次键(第一条非空瓦片地址)⇒ 后面那句自然判重,一片瓦片终身只 `fetch` 一次。
417
+ * 队列怎么排(`panoTileStitcher` 的 `_queue`,一处排队一张尺):
418
+ * ① **模型与平面图天然在最前面** —— 这两族的下载不在拼合器里,`importData` 当场同步发起
419
+ * (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
420
+ * 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
421
+ * ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
422
+ * ② 本方法把存档 0 号、1 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
423
+ * 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
424
+ * 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
425
+ * ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
426
+ * 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
427
+ * ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
428
+ * 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
429
+ * `ensure` 之前 ⇒ 每帧把这两记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以 0 号恒在 1 号前)。
430
+ * ⚠️ 代价如实记:
431
+ * · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前两颗的那面墙会多灰一会儿。
432
+ * · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
433
+ * (`_dead`),退避重试期间队首不落定 ⇒ 第 2 颗跟着延后。真实数据里缺片是「地址为空/404」,
434
+ * 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
435
+ * · 这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
436
+ * (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这两个景点迟早要建。
475
437
  * ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
476
438
  * 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
477
439
  * 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
478
- * ⚠️ 代价如实记:这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
479
- * (现行口径每颗全景 2 槽 × 6144² ≈ 400MB 显存)—— 组在运行中不淘汰,走进那两个景点迟早要建,
480
- * 这里只是把这一笔挪到了载入的第一帧。
481
440
  */
482
441
  prefetchHeadPanoramas(): void;
483
442
  /** 释放 GPU 资源:瓦片缓存(**只有这里**清,2026-10-02 决定)、拼合器、平面图 / 模型 / 玻璃面三层、几何、材质 */
@@ -23,7 +23,7 @@
23
23
  * 说明:所有实例共享同一份 material(单一 panorama uniform),
24
24
  * 同一时刻只激活名册 0 号槽(中心)那一个纹理(全局共享单激活方案)。
25
25
  */
26
- import { BufferGeometry, BackSide, EquirectangularReflectionMapping, Float32BufferAttribute, Matrix4, Mesh, Shape, ShapeGeometry, SRGBColorSpace, Vector3, Vector4, } from "three";
26
+ import { BufferGeometry, BackSide, Float32BufferAttribute, Matrix4, Mesh, Shape, ShapeGeometry, SRGBColorSpace, Vector3, Vector4, } from "three";
27
27
  import { createPlaceholderTexture, isPanoTexMode, mktPanoTiledFlag, mktPanoTexUrls, isRtStitchKey, rtStitchKeyParts, RT_STITCH_KEY_PREFIX, RT_STITCH_SLOT_COUNT } from "./mktShader";
28
28
  import { MKT_PANO_MAX_SLOTS } from "./mktMappingShader";
29
29
  import { createMktFloorMappingShaderMaterial } from "./mktMappingShader";
@@ -51,9 +51,6 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
51
51
  import { ImagePlane } from "./imagePlane";
52
52
  import { ModelSlot } from "./modelSlot";
53
53
  import { ImagePlaneTileLoader } from "./imageTileLoader";
54
- // ⭐ 2026-10-28 需求「正方的面 + 双面 + 反射用第一个全景 + 进 json 回显」:第三层,形状与上面两层同口径
55
- // (挂在本网格子树、随展品一起走;数据由自己的 `toData/applyData` 逐字存)。
56
- import { GlassPane } from "./glassPane";
57
54
  /** 载入 JSON 时**优先**加载几个全景的素材(2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」)。
58
55
  * 口径 = 存档 `textures` 数组的前 n 项;其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。 */
59
56
  const PANO_IMPORT_PRIORITY = 2;
@@ -191,23 +188,6 @@ export default class MktMesh extends Mesh {
191
188
  imagePlanes = [];
192
189
  /** 模型槽层(同一棵子树,口径同上) */
193
190
  models = [];
194
- /**
195
- * 玻璃面层(⭐ 2026-10-28 需求「正方的面」):同一棵子树,口径与上面两层一致。
196
- * 与那两层的区别只在材质(MeshPhysicalMaterial + transmission + DoubleSide,见 `./glassPane.ts`)
197
- * ⇒ 它**没有自己的内容纹理**,反射源共用下面那一张 `_glassEnvTex`。
198
- */
199
- glassPanes = [];
200
- /**
201
- * 玻璃面反射环境的**源地址** = 第一个全景的整图地址(`textures[0].url`),空串 = 还没有全景可用。
202
- * ⚠️ 这里存的是**未拼尺寸参数的原始地址**(与 `_texCache` 的键同一口径)⇒ 全景换了地址才发现要重载;
203
- * 下载时按当前 `_ossSize` 档过 `resolveOssUrl`(默认为 1024 那一档)。PMREM 卷积输出的分辨率上限远小于 1024
204
- * ⇒ 反射精度由那一步决定,用缩放副本不降低观感,换来的是省掉整张原图(实测 11904×5952 ≈ 283MB)的解码内存。
205
- * ⚠️ 为什么不用「第一个全景那两张拼合槽 RT」:每张 RT 装的是原图**左/右半幅**且压成 1:1 方图,
206
- * 当等距柱状环境采会横向拉伸一倍 + 缺半边;且它的 mapping 是 UVMapping,three 不会替它做 PMREM ⇒ 采出花屏。
207
- */
208
- _glassEnvKey = "";
209
- /** 已就绪的反射环境纹理(null = 未就绪 / 没有全景);同一张共用给所有玻璃面 ⇒ 只下载、只卷积一次 */
210
- _glassEnvTex = null;
211
191
  /**
212
192
  * 那 4 张瓦片的下载/解码缓存。⭐ 与全景 `_texCache` 同一条旨(10-02「纹理都缓存在对象里面,执行 dispose 时才销毁」)
213
193
  * ⇒ 切工程**不**清它,回来那一圈直接命中缓存、不会再空一格;只随本对象 `dispose()` 释放。
@@ -1211,91 +1191,6 @@ export default class MktMesh extends Mesh {
1211
1191
  if (ms.mountStaged() && onEachLoad)
1212
1192
  onEachLoad();
1213
1193
  }
1214
- // ───────────── 玻璃面层(⭐ 2026-10-28 需求「正方的面:双面 + 反射用第一个全景 + 进 json 回显」) ─────────────
1215
- /** 单颗玻璃面入层(编辑态双击落点用):进数组 + 挂进本对象子树 + 先吃当前反射源,再确认源在下路没有 */
1216
- addGlassPane(gp) {
1217
- this.glassPanes.push(gp);
1218
- this.add(gp);
1219
- gp.setEnvironmentTexture(this._glassEnvTex);
1220
- this.syncGlassEnvironment();
1221
- return gp;
1222
- }
1223
- /** 摘掉一颗并释放它的几何/材质;反射纹理是共用那张 ⇒ 最后一颗也摘掉时才由 `syncGlassEnvironment` 收(按源地址记账,⛔ 不在这里直接 dispose) */
1224
- removeGlassPane(gp) {
1225
- const i = this.glassPanes.indexOf(gp);
1226
- if (i >= 0)
1227
- this.glassPanes.splice(i, 1);
1228
- this.remove(gp);
1229
- gp.dispose();
1230
- this.syncGlassEnvironment();
1231
- }
1232
- /**
1233
- * 整层重建玻璃面(`importData` 与预览那一路)。玻璃面不引用任何素材文件 ⇒ ⛔ 不过 `absAssetUrl`
1234
- * (地址只有反射源那一处,而它取的是**本对象内存里**的 `textures[0].url`,已经是补过域名的形态)。
1235
- */
1236
- setGlassPanes(datas) {
1237
- for (const gp of this.glassPanes) {
1238
- this.remove(gp);
1239
- gp.dispose();
1240
- }
1241
- this.glassPanes = [];
1242
- for (const d of datas) {
1243
- const gp = new GlassPane(d.name);
1244
- gp.applyData(d);
1245
- this.glassPanes.push(gp);
1246
- this.add(gp);
1247
- }
1248
- this.syncGlassEnvironment();
1249
- }
1250
- /**
1251
- * 把「第一个全景」的整图当成玻璃面的反射环境(2026-10-28 需求)。
1252
- *
1253
- * 判据 = 源地址变了没有(⛔ 不按下标记:删掉 0 号景点后原 1 号自动顶上来,那时地址会变 ⇒ 自愈)。
1254
- * 调用点只有四处,⛔ 不进 `onBeforeRender` 自驱:① 单颗入层 ② 单颗摘除 ③ 整层重建 ④ 编辑态每帧 tick 里与
1255
- * `feedImagePlaneTiles()` 并排那一句(源地址没变就一句比较直接返回 ⇒ 无玻璃面的场景零成本,
1256
- * 而全景被增删/换图那些路都不必各自记得再来点一次)。
1257
- *
1258
- * ⚠️ 换源那一步必须**先摘后放**:材质还指着这张、位图却已 `close()` ⇒ 采出来是空图。
1259
- * ⚠️ 下载走 `loadPanoTexture`(解码在工作线程)并自动向 `DefaultLoadingManager` 记账 ⇒ 预览弹窗那个
1260
- * 加载进度条能看见它,与全景瓦片同一本账。
1261
- * ⚠️ 结果只存进 `_glassEnvTex`,⛔ 不进 `_texCache`:那张表在 `setOssSize` 换档时会被整批 dispose
1262
- * (`_reloadActiveTexture`),而正在被材质引用的这张不能被那样顺手清掉。
1263
- */
1264
- syncGlassEnvironment() {
1265
- // 没有玻璃面 ⇒ 源地址按「空」算:下面那一段照常走换源流程,把共用那张位图关干净(⛔ 不让它悬着)
1266
- const key = this.glassPanes.length === 0 ? "" : (this.textures[0]?.url ?? "");
1267
- if (key === this._glassEnvKey)
1268
- return;
1269
- this._glassEnvKey = key;
1270
- if (this._glassEnvTex) {
1271
- this._applyGlassEnv(null);
1272
- releasePanoTexture(this._glassEnvTex);
1273
- this._glassEnvTex = null;
1274
- }
1275
- if (!key)
1276
- return;
1277
- loadPanoTexture(this.resolveOssUrl(key, false)).then((tex) => {
1278
- // 这一张到货时源可能已被后来的换源(切项目 / 换全景)顶掉 ⇒ 就地释放,⛔ 不许写回材质
1279
- if (this._glassEnvKey !== key) {
1280
- releasePanoTexture(tex);
1281
- return;
1282
- }
1283
- tex.colorSpace = SRGBColorSpace;
1284
- // three 的 `WebGLCubeUVMaps` 只对这个标记才自动 PMREM 卷积(示例也是把等距柱状图直接挂 material.envMap)
1285
- tex.mapping = EquirectangularReflectionMapping;
1286
- this._glassEnvTex = tex;
1287
- this._applyGlassEnv(tex);
1288
- }, () => {
1289
- // 单次失败:清掉记账 ⇒ 下一次有人调本方法(新增/重建玻璃面、编辑态 tick)自然重试,
1290
- // 与全景瓦片那条「失败后下一帧同键再试」同口径,只是这里由调用点驱动而非每帧驱动。
1291
- this._glassEnvKey = "";
1292
- });
1293
- }
1294
- /** 把同一张反射源发给每一颗玻璃面(`GlassPane` 内部按对象判等 ⇒ 重复发同一张不会重编译)。 */
1295
- _applyGlassEnv(tex) {
1296
- for (const gp of this.glassPanes)
1297
- gp.setEnvironmentTexture(tex);
1298
- }
1299
1194
  /**
1300
1195
  * 把各颗平面那 4 张瓦片的纹理绑进材质(10-22 晚定的口径:不走拼合器 RT)。
1301
1196
  * 2026-10-26 决定改成**逐帧**:一次把整层地址全丢给下载器(下载/解码在网络与工作线程侧,不占这一帧),
@@ -1417,9 +1312,6 @@ export default class MktMesh extends Mesh {
1417
1312
  const d = ms.toData();
1418
1313
  return { ...d, url: d.url ? stripAssetHost(d.url, pn) : "" };
1419
1314
  }),
1420
- // 玻璃面层(2026-10-28 需求):只有位置/旋转/边长,⛔ 不过 `stripAssetHost` —— 它不引用任何素材,
1421
- // 反射源是运行时从 `textures[0].url` 取的(上面那个键自己就落进存档了)。
1422
- glass: this.glassPanes.map((gp) => gp.toData()),
1423
1315
  };
1424
1316
  }
1425
1317
  /** 导入数据:重建墙体几何与纹理列表,并激活第一个纹理
@@ -1475,51 +1367,48 @@ export default class MktMesh extends Mesh {
1475
1367
  tiles: (d.tiles ?? []).map((u) => absAssetUrl(u, pn)),
1476
1368
  })));
1477
1369
  this.setModels((data.models ?? []).map((d) => ({ ...d, url: d.url ? absAssetUrl(d.url, pn) : "" })), onModelLoad);
1478
- // 玻璃面层(2026-10-28 需求):排在 setTextures **之后**是硬要求 —— 它的反射源读的就是刚还原好的
1479
- // `textures[0].url`(补过域名的形态),排在前面会用上一次项目的地址起一次错的载。
1480
- // 旧存档没有这个键 ⇒ 空数组 = 整层清空,与上面两层的口径逐字一致。
1481
- this.setGlassPanes(data.glass ?? []);
1482
1370
  }
1483
1371
  /**
1484
- * 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**的素材点名抢在别的之前加载(2026-10-29 需求)。
1372
+ * 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**按存档顺序登记进拼合器的**加载任务队列**
1373
+ *(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
1374
+ * 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
1375
+ * 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
1376
+ * 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」)。
1485
1377
  * 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
1486
1378
  *(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
1487
1379
  *
1488
- * 为什么能「先加载」:拼合器每帧那三道工序(补下载 → 画已进 GPU 的 → 再传一张)都按
1489
- * `_groups` 这张 Map 的**在册顺序**取料,而下载额度只有「每帧 2 张 / 在途 ≤8 / 已解码待上传 ≤8」
1490
- * (`panoTileStitcher.ts` 顶部那三个常数)⇒ 排在头部的两组把额度吃满,其余全景要等它们画满才轮得到。
1491
- * 平时这个顺序由每帧的槽位名册定(离注视点近的先下),本方法把它钉成「存档数组的前两个」,与机位无关。
1492
- * ⚠️⚠️ 为什么点名时要带第三个参 `front = true`(每帧把这两批**挪回队首**)而⛔ 不只是「先入册」:
1493
- * 在册顺序是拼合器自己的状态 —— 后入册的只会**追加到队尾**,而 `ensure` 幂等、已在册就不再移动。
1494
- * 预览这条路恰好是最坏的那种排法:`feedFaceMapping` 开头还有一记 `pump`,拼合器**第一次**拿到渲染器时
1495
- * 必走 `_switchRenderer`(`null → 真 renderer` 也算换,弹窗每开一次就是换一次)把在册组整份作废,
1496
- * 随后名册按距离重新入册占了队首 ⇒ 本方法下一帧重新入册只能排在它们后面,而且**从此再也翻不了身**。
1497
- * 2026-10-29 逐帧打点就是这个形状:帧 1 `p0,p1,p11…p4` → 帧 2 起 `p11…p4,p0,p1`,
1498
- * 40 帧下来 p0 只下到 1 片、p1 下到 30 片,离注视点最近那颗第 13 条请求就开始了 = 线上报的现象。
1499
- * ⇒ 带 `front` 之后顺序每帧重新成立,与谁先入册、作废过几次、机位在哪都无关
1500
- * (真跑自证在护栏 BE ④:`D` 后入册排队尾,带 `front` 再点一次 ⇒ 立刻回到队首、下一帧下载全归它)。
1501
- * ⚠️ 为什么是**每帧**而不是「载入那一次点一记」:上面那次作废就发生在载入之后的第一帧,
1502
- * 只在 `importData` 里点名等于白点;本方法点在 `pump` 之后、名册逐槽 `ensure` 之前,且每帧都点
1503
- * (已在册且已在队首 ⇒ `_toFront` 里一次 `keys().next()` 就返回,没有额外开销)。
1504
- * ⛔ 不另立第二把下载尺:这里用的就是 `feedFaceMapping` 每帧在用的同一个 `ensure(info.tiles, …)`
1505
- * 与同一个批次键(第一条非空瓦片地址)⇒ 后面那句自然判重,一片瓦片终身只 `fetch` 一次。
1380
+ * 队列怎么排(`panoTileStitcher` 的 `_queue`,一处排队一张尺):
1381
+ * ① **模型与平面图天然在最前面** —— 这两族的下载不在拼合器里,`importData` 当场同步发起
1382
+ * (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
1383
+ * 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
1384
+ * ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
1385
+ * ② 本方法把存档 0 号、1 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
1386
+ * 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
1387
+ * 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
1388
+ * ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
1389
+ * 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
1390
+ * ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
1391
+ * 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
1392
+ * `ensure` 之前 ⇒ 每帧把这两记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以 0 号恒在 1 号前)。
1393
+ * ⚠️ 代价如实记:
1394
+ * · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前两颗的那面墙会多灰一会儿。
1395
+ * · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
1396
+ * (`_dead`),退避重试期间队首不落定 ⇒ 第 2 颗跟着延后。真实数据里缺片是「地址为空/404」,
1397
+ * 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
1398
+ * · 这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
1399
+ * (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这两个景点迟早要建。
1506
1400
  * ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
1507
1401
  * 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
1508
1402
  * 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
1509
- * ⚠️ 代价如实记:这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
1510
- * (现行口径每颗全景 2 槽 × 6144² ≈ 400MB 显存)—— 组在运行中不淘汰,走进那两个景点迟早要建,
1511
- * 这里只是把这一笔挪到了载入的第一帧。
1512
1403
  */
1513
1404
  prefetchHeadPanoramas() {
1514
1405
  const n = Math.min(PANO_IMPORT_PRIORITY, this.textures.length);
1515
- // ⚠️ **倒着**点:`front` 是「挪到最前」,正着点会让 0 号被 1 号压到后面(实测队首变成 `p1,p0`)
1516
- // ⇒ 倒着点,最后一手挪的是 0 号,存档序号小的仍排在更前。
1517
- for (let i = n - 1; i >= 0; i--) {
1406
+ // 正着点:0 号先入队 ⇒ 队首是它,落定后才轮到 1 号(`prioritize` 对已在队列里的键不挪位置)
1407
+ for (let i = 0; i < n; i++) {
1518
1408
  const info = this.textures[i];
1519
1409
  // 与 `feedFaceMapping` 里那一句同判据:真瓦片才进拼合器,整图形态进瓦片缓存
1520
- // 第三个参 `true` = 把这批**挪到在册顺序最前**(⛔ 光靠「先入册」押不住顺序,见方法头)
1521
1410
  if (mktPanoTiledFlag(info.tiles, this._panoTexMode) === 1)
1522
- this._stitcher.ensure(info.tiles, undefined, true);
1411
+ this._stitcher.prioritize(info.tiles);
1523
1412
  else if (info.url)
1524
1413
  this.ensureTileTexture(info.url, false);
1525
1414
  }
@@ -1548,16 +1437,6 @@ export default class MktMesh extends Mesh {
1548
1437
  ms.dispose();
1549
1438
  }
1550
1439
  this.models = [];
1551
- // 玻璃面层:几何/材质各归各颗释放;那张共用的反射纹理在这里连位图一起关
1552
- // (⛔ 它从不在 `_texCache` 里 ⇒ 上面那一轮清不到它,漏了这一句就是每次换页漏一张全景整图)。
1553
- for (const gp of this.glassPanes) {
1554
- this.remove(gp);
1555
- gp.dispose();
1556
- }
1557
- this.glassPanes = [];
1558
- releasePanoTexture(this._glassEnvTex);
1559
- this._glassEnvTex = null;
1560
- this._glassEnvKey = "";
1561
1440
  this._imgTileLoader.clear();
1562
1441
  this._imgBindQueued.clear();
1563
1442
  this._imgBindDone.clear();
@@ -28,7 +28,6 @@ import {
28
28
  BufferGeometry,
29
29
  Camera,
30
30
  BackSide,
31
- EquirectangularReflectionMapping,
32
31
  Float32BufferAttribute,
33
32
  Material,
34
33
  Matrix4,
@@ -72,9 +71,6 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
72
71
  import { ImagePlane, type ImagePlaneData } from "./imagePlane";
73
72
  import { ModelSlot, type ModelSlotData } from "./modelSlot";
74
73
  import { ImagePlaneTileLoader } from "./imageTileLoader";
75
- // ⭐ 2026-10-28 需求「正方的面 + 双面 + 反射用第一个全景 + 进 json 回显」:第三层,形状与上面两层同口径
76
- // (挂在本网格子树、随展品一起走;数据由自己的 `toData/applyData` 逐字存)。
77
- import { GlassPane, type GlassPaneData } from "./glassPane";
78
74
 
79
75
  /** 载入 JSON 时**优先**加载几个全景的素材(2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」)。
80
76
  * 口径 = 存档 `textures` 数组的前 n 项;其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。 */
@@ -219,23 +215,6 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
219
215
  imagePlanes: ImagePlane[] = [];
220
216
  /** 模型槽层(同一棵子树,口径同上) */
221
217
  models: ModelSlot[] = [];
222
- /**
223
- * 玻璃面层(⭐ 2026-10-28 需求「正方的面」):同一棵子树,口径与上面两层一致。
224
- * 与那两层的区别只在材质(MeshPhysicalMaterial + transmission + DoubleSide,见 `./glassPane.ts`)
225
- * ⇒ 它**没有自己的内容纹理**,反射源共用下面那一张 `_glassEnvTex`。
226
- */
227
- glassPanes: GlassPane[] = [];
228
- /**
229
- * 玻璃面反射环境的**源地址** = 第一个全景的整图地址(`textures[0].url`),空串 = 还没有全景可用。
230
- * ⚠️ 这里存的是**未拼尺寸参数的原始地址**(与 `_texCache` 的键同一口径)⇒ 全景换了地址才发现要重载;
231
- * 下载时按当前 `_ossSize` 档过 `resolveOssUrl`(默认为 1024 那一档)。PMREM 卷积输出的分辨率上限远小于 1024
232
- * ⇒ 反射精度由那一步决定,用缩放副本不降低观感,换来的是省掉整张原图(实测 11904×5952 ≈ 283MB)的解码内存。
233
- * ⚠️ 为什么不用「第一个全景那两张拼合槽 RT」:每张 RT 装的是原图**左/右半幅**且压成 1:1 方图,
234
- * 当等距柱状环境采会横向拉伸一倍 + 缺半边;且它的 mapping 是 UVMapping,three 不会替它做 PMREM ⇒ 采出花屏。
235
- */
236
- private _glassEnvKey: string = "";
237
- /** 已就绪的反射环境纹理(null = 未就绪 / 没有全景);同一张共用给所有玻璃面 ⇒ 只下载、只卷积一次 */
238
- private _glassEnvTex: Texture | null = null;
239
218
  /**
240
219
  * 那 4 张瓦片的下载/解码缓存。⭐ 与全景 `_texCache` 同一条旨(10-02「纹理都缓存在对象里面,执行 dispose 时才销毁」)
241
220
  * ⇒ 切工程**不**清它,回来那一圈直接命中缓存、不会再空一格;只随本对象 `dispose()` 释放。
@@ -1247,96 +1226,6 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
1247
1226
  if (ms.mountStaged() && onEachLoad) onEachLoad();
1248
1227
  }
1249
1228
 
1250
- // ───────────── 玻璃面层(⭐ 2026-10-28 需求「正方的面:双面 + 反射用第一个全景 + 进 json 回显」) ─────────────
1251
-
1252
- /** 单颗玻璃面入层(编辑态双击落点用):进数组 + 挂进本对象子树 + 先吃当前反射源,再确认源在下路没有 */
1253
- addGlassPane(gp: GlassPane): GlassPane {
1254
- this.glassPanes.push(gp);
1255
- this.add(gp);
1256
- gp.setEnvironmentTexture(this._glassEnvTex);
1257
- this.syncGlassEnvironment();
1258
- return gp;
1259
- }
1260
-
1261
- /** 摘掉一颗并释放它的几何/材质;反射纹理是共用那张 ⇒ 最后一颗也摘掉时才由 `syncGlassEnvironment` 收(按源地址记账,⛔ 不在这里直接 dispose) */
1262
- removeGlassPane(gp: GlassPane): void {
1263
- const i = this.glassPanes.indexOf(gp);
1264
- if (i >= 0) this.glassPanes.splice(i, 1);
1265
- this.remove(gp);
1266
- gp.dispose();
1267
- this.syncGlassEnvironment();
1268
- }
1269
-
1270
- /**
1271
- * 整层重建玻璃面(`importData` 与预览那一路)。玻璃面不引用任何素材文件 ⇒ ⛔ 不过 `absAssetUrl`
1272
- * (地址只有反射源那一处,而它取的是**本对象内存里**的 `textures[0].url`,已经是补过域名的形态)。
1273
- */
1274
- setGlassPanes(datas: GlassPaneData[]): void {
1275
- for (const gp of this.glassPanes) {
1276
- this.remove(gp);
1277
- gp.dispose();
1278
- }
1279
- this.glassPanes = [];
1280
- for (const d of datas) {
1281
- const gp = new GlassPane(d.name);
1282
- gp.applyData(d);
1283
- this.glassPanes.push(gp);
1284
- this.add(gp);
1285
- }
1286
- this.syncGlassEnvironment();
1287
- }
1288
-
1289
- /**
1290
- * 把「第一个全景」的整图当成玻璃面的反射环境(2026-10-28 需求)。
1291
- *
1292
- * 判据 = 源地址变了没有(⛔ 不按下标记:删掉 0 号景点后原 1 号自动顶上来,那时地址会变 ⇒ 自愈)。
1293
- * 调用点只有四处,⛔ 不进 `onBeforeRender` 自驱:① 单颗入层 ② 单颗摘除 ③ 整层重建 ④ 编辑态每帧 tick 里与
1294
- * `feedImagePlaneTiles()` 并排那一句(源地址没变就一句比较直接返回 ⇒ 无玻璃面的场景零成本,
1295
- * 而全景被增删/换图那些路都不必各自记得再来点一次)。
1296
- *
1297
- * ⚠️ 换源那一步必须**先摘后放**:材质还指着这张、位图却已 `close()` ⇒ 采出来是空图。
1298
- * ⚠️ 下载走 `loadPanoTexture`(解码在工作线程)并自动向 `DefaultLoadingManager` 记账 ⇒ 预览弹窗那个
1299
- * 加载进度条能看见它,与全景瓦片同一本账。
1300
- * ⚠️ 结果只存进 `_glassEnvTex`,⛔ 不进 `_texCache`:那张表在 `setOssSize` 换档时会被整批 dispose
1301
- * (`_reloadActiveTexture`),而正在被材质引用的这张不能被那样顺手清掉。
1302
- */
1303
- syncGlassEnvironment(): void {
1304
- // 没有玻璃面 ⇒ 源地址按「空」算:下面那一段照常走换源流程,把共用那张位图关干净(⛔ 不让它悬着)
1305
- const key = this.glassPanes.length === 0 ? "" : (this.textures[0]?.url ?? "");
1306
- if (key === this._glassEnvKey) return;
1307
- this._glassEnvKey = key;
1308
- if (this._glassEnvTex) {
1309
- this._applyGlassEnv(null);
1310
- releasePanoTexture(this._glassEnvTex);
1311
- this._glassEnvTex = null;
1312
- }
1313
- if (!key) return;
1314
- loadPanoTexture(this.resolveOssUrl(key, false)).then(
1315
- (tex) => {
1316
- // 这一张到货时源可能已被后来的换源(切项目 / 换全景)顶掉 ⇒ 就地释放,⛔ 不许写回材质
1317
- if (this._glassEnvKey !== key) {
1318
- releasePanoTexture(tex);
1319
- return;
1320
- }
1321
- tex.colorSpace = SRGBColorSpace;
1322
- // three 的 `WebGLCubeUVMaps` 只对这个标记才自动 PMREM 卷积(示例也是把等距柱状图直接挂 material.envMap)
1323
- tex.mapping = EquirectangularReflectionMapping;
1324
- this._glassEnvTex = tex;
1325
- this._applyGlassEnv(tex);
1326
- },
1327
- () => {
1328
- // 单次失败:清掉记账 ⇒ 下一次有人调本方法(新增/重建玻璃面、编辑态 tick)自然重试,
1329
- // 与全景瓦片那条「失败后下一帧同键再试」同口径,只是这里由调用点驱动而非每帧驱动。
1330
- this._glassEnvKey = "";
1331
- }
1332
- );
1333
- }
1334
-
1335
- /** 把同一张反射源发给每一颗玻璃面(`GlassPane` 内部按对象判等 ⇒ 重复发同一张不会重编译)。 */
1336
- private _applyGlassEnv(tex: Texture | null): void {
1337
- for (const gp of this.glassPanes) gp.setEnvironmentTexture(tex);
1338
- }
1339
-
1340
1229
  /**
1341
1230
  * 把各颗平面那 4 张瓦片的纹理绑进材质(10-22 晚定的口径:不走拼合器 RT)。
1342
1231
  * 2026-10-26 决定改成**逐帧**:一次把整层地址全丢给下载器(下载/解码在网络与工作线程侧,不占这一帧),
@@ -1454,9 +1343,6 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
1454
1343
  const d = ms.toData();
1455
1344
  return { ...d, url: d.url ? stripAssetHost(d.url, pn) : "" };
1456
1345
  }),
1457
- // 玻璃面层(2026-10-28 需求):只有位置/旋转/边长,⛔ 不过 `stripAssetHost` —— 它不引用任何素材,
1458
- // 反射源是运行时从 `textures[0].url` 取的(上面那个键自己就落进存档了)。
1459
- glass: this.glassPanes.map((gp) => gp.toData()),
1460
1346
  };
1461
1347
  }
1462
1348
 
@@ -1519,51 +1405,48 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
1519
1405
  (data.models ?? []).map((d) => ({ ...d, url: d.url ? absAssetUrl(d.url, pn) : "" })),
1520
1406
  onModelLoad
1521
1407
  );
1522
- // 玻璃面层(2026-10-28 需求):排在 setTextures **之后**是硬要求 —— 它的反射源读的就是刚还原好的
1523
- // `textures[0].url`(补过域名的形态),排在前面会用上一次项目的地址起一次错的载。
1524
- // 旧存档没有这个键 ⇒ 空数组 = 整层清空,与上面两层的口径逐字一致。
1525
- this.setGlassPanes(data.glass ?? []);
1526
1408
  }
1527
1409
 
1528
1410
  /**
1529
- * 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**的素材点名抢在别的之前加载(2026-10-29 需求)。
1411
+ * 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**按存档顺序登记进拼合器的**加载任务队列**
1412
+ *(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
1413
+ * 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
1414
+ * 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
1415
+ * 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」)。
1530
1416
  * 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
1531
1417
  *(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
1532
1418
  *
1533
- * 为什么能「先加载」:拼合器每帧那三道工序(补下载 → 画已进 GPU 的 → 再传一张)都按
1534
- * `_groups` 这张 Map 的**在册顺序**取料,而下载额度只有「每帧 2 张 / 在途 ≤8 / 已解码待上传 ≤8」
1535
- * (`panoTileStitcher.ts` 顶部那三个常数)⇒ 排在头部的两组把额度吃满,其余全景要等它们画满才轮得到。
1536
- * 平时这个顺序由每帧的槽位名册定(离注视点近的先下),本方法把它钉成「存档数组的前两个」,与机位无关。
1537
- * ⚠️⚠️ 为什么点名时要带第三个参 `front = true`(每帧把这两批**挪回队首**)而⛔ 不只是「先入册」:
1538
- * 在册顺序是拼合器自己的状态 —— 后入册的只会**追加到队尾**,而 `ensure` 幂等、已在册就不再移动。
1539
- * 预览这条路恰好是最坏的那种排法:`feedFaceMapping` 开头还有一记 `pump`,拼合器**第一次**拿到渲染器时
1540
- * 必走 `_switchRenderer`(`null → 真 renderer` 也算换,弹窗每开一次就是换一次)把在册组整份作废,
1541
- * 随后名册按距离重新入册占了队首 ⇒ 本方法下一帧重新入册只能排在它们后面,而且**从此再也翻不了身**。
1542
- * 2026-10-29 逐帧打点就是这个形状:帧 1 `p0,p1,p11…p4` → 帧 2 起 `p11…p4,p0,p1`,
1543
- * 40 帧下来 p0 只下到 1 片、p1 下到 30 片,离注视点最近那颗第 13 条请求就开始了 = 线上报的现象。
1544
- * ⇒ 带 `front` 之后顺序每帧重新成立,与谁先入册、作废过几次、机位在哪都无关
1545
- * (真跑自证在护栏 BE ④:`D` 后入册排队尾,带 `front` 再点一次 ⇒ 立刻回到队首、下一帧下载全归它)。
1546
- * ⚠️ 为什么是**每帧**而不是「载入那一次点一记」:上面那次作废就发生在载入之后的第一帧,
1547
- * 只在 `importData` 里点名等于白点;本方法点在 `pump` 之后、名册逐槽 `ensure` 之前,且每帧都点
1548
- * (已在册且已在队首 ⇒ `_toFront` 里一次 `keys().next()` 就返回,没有额外开销)。
1549
- * ⛔ 不另立第二把下载尺:这里用的就是 `feedFaceMapping` 每帧在用的同一个 `ensure(info.tiles, …)`
1550
- * 与同一个批次键(第一条非空瓦片地址)⇒ 后面那句自然判重,一片瓦片终身只 `fetch` 一次。
1419
+ * 队列怎么排(`panoTileStitcher` 的 `_queue`,一处排队一张尺):
1420
+ * ① **模型与平面图天然在最前面** —— 这两族的下载不在拼合器里,`importData` 当场同步发起
1421
+ * (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
1422
+ * 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
1423
+ * ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
1424
+ * ② 本方法把存档 0 号、1 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
1425
+ * 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
1426
+ * 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
1427
+ * ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
1428
+ * 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
1429
+ * ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
1430
+ * 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
1431
+ * `ensure` 之前 ⇒ 每帧把这两记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以 0 号恒在 1 号前)。
1432
+ * ⚠️ 代价如实记:
1433
+ * · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前两颗的那面墙会多灰一会儿。
1434
+ * · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
1435
+ * (`_dead`),退避重试期间队首不落定 ⇒ 第 2 颗跟着延后。真实数据里缺片是「地址为空/404」,
1436
+ * 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
1437
+ * · 这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
1438
+ * (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这两个景点迟早要建。
1551
1439
  * ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
1552
1440
  * 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
1553
1441
  * 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
1554
- * ⚠️ 代价如实记:这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
1555
- * (现行口径每颗全景 2 槽 × 6144² ≈ 400MB 显存)—— 组在运行中不淘汰,走进那两个景点迟早要建,
1556
- * 这里只是把这一笔挪到了载入的第一帧。
1557
1442
  */
1558
1443
  prefetchHeadPanoramas(): void {
1559
1444
  const n = Math.min(PANO_IMPORT_PRIORITY, this.textures.length);
1560
- // ⚠️ **倒着**点:`front` 是「挪到最前」,正着点会让 0 号被 1 号压到后面(实测队首变成 `p1,p0`)
1561
- // ⇒ 倒着点,最后一手挪的是 0 号,存档序号小的仍排在更前。
1562
- for (let i = n - 1; i >= 0; i--) {
1445
+ // 正着点:0 号先入队 ⇒ 队首是它,落定后才轮到 1 号(`prioritize` 对已在队列里的键不挪位置)
1446
+ for (let i = 0; i < n; i++) {
1563
1447
  const info = this.textures[i];
1564
1448
  // 与 `feedFaceMapping` 里那一句同判据:真瓦片才进拼合器,整图形态进瓦片缓存
1565
- // 第三个参 `true` = 把这批**挪到在册顺序最前**(⛔ 光靠「先入册」押不住顺序,见方法头)
1566
- if (mktPanoTiledFlag(info.tiles, this._panoTexMode) === 1) this._stitcher.ensure(info.tiles, undefined, true);
1449
+ if (mktPanoTiledFlag(info.tiles, this._panoTexMode) === 1) this._stitcher.prioritize(info.tiles);
1567
1450
  else if (info.url) this.ensureTileTexture(info.url, false);
1568
1451
  }
1569
1452
  }
@@ -1592,16 +1475,6 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
1592
1475
  ms.dispose();
1593
1476
  }
1594
1477
  this.models = [];
1595
- // 玻璃面层:几何/材质各归各颗释放;那张共用的反射纹理在这里连位图一起关
1596
- // (⛔ 它从不在 `_texCache` 里 ⇒ 上面那一轮清不到它,漏了这一句就是每次换页漏一张全景整图)。
1597
- for (const gp of this.glassPanes) {
1598
- this.remove(gp);
1599
- gp.dispose();
1600
- }
1601
- this.glassPanes = [];
1602
- releasePanoTexture(this._glassEnvTex);
1603
- this._glassEnvTex = null;
1604
- this._glassEnvKey = "";
1605
1478
  this._imgTileLoader.clear();
1606
1479
  this._imgBindQueued.clear();
1607
1480
  this._imgBindDone.clear();