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.
- package/MktMesh/MktMesh.d.ts +25 -66
- package/MktMesh/MktMesh.js +29 -150
- package/MktMesh/MktMesh.ts +28 -155
- package/MktMesh/panoTileStitcher.d.ts +36 -11
- package/MktMesh/panoTileStitcher.js +89 -47
- package/MktMesh/panoTileStitcher.ts +94 -42
- package/MktMesh/types.d.ts +0 -6
- package/MktMesh/types.ts +0 -6
- package/package.json +1 -6
- package/MktMesh/glassPane.d.ts +0 -43
- package/MktMesh/glassPane.js +0 -97
- package/MktMesh/glassPane.ts +0 -116
|
@@ -8,10 +8,21 @@ export interface PanoStitcher {
|
|
|
8
8
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
9
9
|
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 8×4 / 2 槽 / 6144×6144,2026-10-27 需求);
|
|
10
10
|
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
ensure(urls: string[], layout?: TileLayout
|
|
11
|
+
* ⚠️ 2026-10-30「加载任务队列」:本方法只负责**入册**(新组追加到在册顺序末尾 ⇒ 排在队列里等),
|
|
12
|
+
* 点名优先的那一路换成交换 `prioritize()`(10-29 那版挂在第三个参 `front` 上的「挪到队首」已并进那里,
|
|
13
|
+
* ⛔ 一处排队只有 `prioritize` 一张尺)。 */
|
|
14
|
+
ensure(urls: string[], layout?: TileLayout): void;
|
|
15
|
+
/**
|
|
16
|
+
* 把某批瓦片登记进**加载任务队列**(2026-10-30 需求,逐字:「依次把前 2 个全景的顺序把瓦片加载任务添加到
|
|
17
|
+
* 任务队列里面,第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;后续按距离需要哪个全景
|
|
18
|
+
* 的时候同样添加到队列后面等待加载」)。
|
|
19
|
+
* 语义 = 先 `ensure` 入册(不在册就建组),再把批次键登记进队列**末尾**(已在队列里就不再挪,所以先登记的排前面)。
|
|
20
|
+
* 排队规则见 `_pumpDownloads`:**队首那组落定(32 格全画进 RT,或该格本就缺地址/判死)之前,队列里后面的组
|
|
21
|
+
* 和一切没登记的名册组一片都不发** ⇒ 第 1 颗全景独享全部下载额度、最快出全图。
|
|
22
|
+
* ⚠️ 消费方每帧点一次(`MktMesh.prefetchHeadPanoramas`):预览每开一次都换渲染器,`_switchRenderer` 会把在册组
|
|
23
|
+
* 整份作废 ⇒ 队列跟着清空,下一帧重新登记才能把顺序抢回来(只在 `importData` 里点一次等于白点)。
|
|
24
|
+
*/
|
|
25
|
+
prioritize(urls: string[], layout?: TileLayout): void;
|
|
15
26
|
/**
|
|
16
27
|
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
17
28
|
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
@@ -40,6 +51,10 @@ export interface PanoStitcher {
|
|
|
40
51
|
export declare class PanoTileStitcher implements PanoStitcher {
|
|
41
52
|
/** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
|
|
42
53
|
private _groups;
|
|
54
|
+
/** 加载任务队列(2026-10-30 需求):批次键的**串行带**,队首那组落定才开下一组。
|
|
55
|
+
* 由 `prioritize` 登记(存档里前 2 颗全景),`_pumpDownloads` 每帧出清;`_switchRenderer`/`dispose`/`disposeGroup`
|
|
56
|
+
* 跟着组一起拆。⛔ 这是「谁先下载」的唯一一把尺 —— 在册顺序(`_groups` 的 Map 序)只决定「同不带队列时」的补料次序。 */
|
|
57
|
+
private _queue;
|
|
43
58
|
/** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
|
|
44
59
|
private _texs;
|
|
45
60
|
/** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
|
|
@@ -77,10 +92,14 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
77
92
|
* ⚠️ 唯一按张数**能混进来**的是 10-06 那版同为 32 片的老档(切片顺序与槽分组两版一致 ⇒ 不会错位),
|
|
78
93
|
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
79
94
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
80
|
-
ensure(urls: string[], layout?: TileLayout
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
ensure(urls: string[], layout?: TileLayout): void;
|
|
96
|
+
/** 登记进加载任务队列(语义与排队规则见接口上那一段)。已在队列里 ⇒ 不挪(先登记的仍在前)。 */
|
|
97
|
+
prioritize(urls: string[], layout?: TileLayout): void;
|
|
98
|
+
/** 地址数组 → 批次键(补齐到满批再取首条非空,与 `ensure` 里同一条尺 ⇒ 两处不会各算各的)。
|
|
99
|
+
* 超限或全空 ⇒ 空串(`ensure` 那一步同样不收,这里也不入队)。 */
|
|
100
|
+
private _batchKeyOf;
|
|
101
|
+
/** 这一组的每一格要么已经画进 RT,要么根本没有地址 / 地址已判死 ⇒ 该下的都下完了(队列出清判据)。 */
|
|
102
|
+
private _isSettled;
|
|
84
103
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
85
104
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
86
105
|
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null;
|
|
@@ -92,14 +111,20 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
92
111
|
progressStamp(): number;
|
|
93
112
|
/** 每帧驱动(三道工序,顺序固定:补下载 → 画已进 GPU 的 → 再传一张新的)。 */
|
|
94
113
|
pump(renderer: WebGLRenderer | null): void;
|
|
95
|
-
/**
|
|
114
|
+
/** 释放一整组(2 张 RT + 该组还没画掉的瓦片纹理);队列里那一记任务跟着撤(组没了还排着 = 队首永远等不到落定) */
|
|
96
115
|
disposeGroup(batch: string): void;
|
|
97
116
|
/** 释放全部(持有者 `dispose()` 时调用);共享的那套 quad/材质/空白纹理一并退掉 */
|
|
98
117
|
dispose(): void;
|
|
99
118
|
private _now;
|
|
100
|
-
/** 工序 1:按「槽 0 格 0 →
|
|
101
|
-
*
|
|
119
|
+
/** 工序 1:按「槽 0 格 0 → …」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 / 下载中 ≤8 /
|
|
120
|
+
* 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。
|
|
121
|
+
* ⭐ 2026-10-30「加载任务队列」:队列非空时**只喂队首那一组**,它落定(`_isSettled`)就出队、下一帧轮到下一个;
|
|
122
|
+
* ⇒ 存档里第 1 颗全景独享额度下满,才轮到第 2 颗,之后才轮到名册按距离点到、排在队尾等着的那些全景。
|
|
123
|
+
* 队列为空 ⇒ 退回原来的在册顺序逐组补料(图片平面那一路不入队,照旧按在册顺序)。 */
|
|
102
124
|
private _pumpDownloads;
|
|
125
|
+
/** 某一组的取料循环:把「还没画、有地址、没判死、没发起过」的格子交给 `_loadTile`。
|
|
126
|
+
* 返回 false = 三道闸里有一道合上了 ⇒ 本帧别再往下看(`ctx` 是连扣的,跨组也接着算)。 */
|
|
127
|
+
private _issueCells;
|
|
103
128
|
/** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
|
|
104
129
|
* ⛔ 准入只看一件事:`uploaded`。没上传过的纹理当场画 = `texImage2D` 藏进 `render()` 里
|
|
105
130
|
* = 每帧一张新纹理那道闸形同虚设(这正是本轮要治的卡顿本体)。 */
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
*
|
|
42
42
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
43
43
|
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
44
|
+
* ⭐ 2026-10-30 需求「加载任务队列」:`_queue` 非空时**只喂队首那一组**,那一组落定才出队开下一组
|
|
45
|
+
* ⇒ 存档前 2 颗全景是一颗一颗下的(第 1 颗下满才轮到第 2 颗),名册按距离点到的排在后面等。
|
|
44
46
|
* 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
|
|
45
47
|
* 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
|
|
46
48
|
* 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
|
|
@@ -166,6 +168,10 @@ void main() {
|
|
|
166
168
|
export class PanoTileStitcher {
|
|
167
169
|
/** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
|
|
168
170
|
_groups = new Map();
|
|
171
|
+
/** 加载任务队列(2026-10-30 需求):批次键的**串行带**,队首那组落定才开下一组。
|
|
172
|
+
* 由 `prioritize` 登记(存档里前 2 颗全景),`_pumpDownloads` 每帧出清;`_switchRenderer`/`dispose`/`disposeGroup`
|
|
173
|
+
* 跟着组一起拆。⛔ 这是「谁先下载」的唯一一把尺 —— 在册顺序(`_groups` 的 Map 序)只决定「同不带队列时」的补料次序。 */
|
|
174
|
+
_queue = [];
|
|
169
175
|
/** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
|
|
170
176
|
_texs = new Map();
|
|
171
177
|
/** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
|
|
@@ -220,20 +226,17 @@ export class PanoTileStitcher {
|
|
|
220
226
|
* ⚠️ 唯一按张数**能混进来**的是 10-06 那版同为 32 片的老档(切片顺序与槽分组两版一致 ⇒ 不会错位),
|
|
221
227
|
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
222
228
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
223
|
-
ensure(urls, layout
|
|
229
|
+
ensure(urls, layout) {
|
|
224
230
|
const L = layout ?? PANO_TILE_LAYOUT;
|
|
225
231
|
const maxTiles = tilesPerBatchOf(L);
|
|
226
232
|
if (!urls || urls.length === 0 || urls.length > maxTiles)
|
|
227
233
|
return;
|
|
234
|
+
const batch = this._batchKeyOf(urls, L);
|
|
235
|
+
if (this._groups.has(batch))
|
|
236
|
+
return;
|
|
228
237
|
const u = urls.slice();
|
|
229
238
|
while (u.length < maxTiles)
|
|
230
239
|
u.push("");
|
|
231
|
-
const batch = rtStitchBatchOf(u);
|
|
232
|
-
if (this._groups.has(batch)) {
|
|
233
|
-
if (front)
|
|
234
|
-
this._toFront(batch);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
240
|
const slotCount = L.slotCount;
|
|
238
241
|
const cellsPerSlot = cellsPerSlotOf(L);
|
|
239
242
|
this._groups.set(batch, {
|
|
@@ -247,25 +250,41 @@ export class PanoTileStitcher {
|
|
|
247
250
|
drawnCount: Array.from({ length: slotCount }, () => 0),
|
|
248
251
|
pending: slotCount,
|
|
249
252
|
});
|
|
250
|
-
if (front)
|
|
251
|
-
this._toFront(batch);
|
|
252
253
|
this._stamp++;
|
|
253
254
|
}
|
|
254
|
-
/**
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
255
|
+
/** 登记进加载任务队列(语义与排队规则见接口上那一段)。已在队列里 ⇒ 不挪(先登记的仍在前)。 */
|
|
256
|
+
prioritize(urls, layout) {
|
|
257
|
+
this.ensure(urls, layout);
|
|
258
|
+
const batch = this._batchKeyOf(urls, layout ?? PANO_TILE_LAYOUT);
|
|
259
|
+
if (!batch || !this._groups.has(batch) || this._queue.includes(batch))
|
|
259
260
|
return;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
261
|
+
this._queue.push(batch);
|
|
262
|
+
}
|
|
263
|
+
/** 地址数组 → 批次键(补齐到满批再取首条非空,与 `ensure` 里同一条尺 ⇒ 两处不会各算各的)。
|
|
264
|
+
* 超限或全空 ⇒ 空串(`ensure` 那一步同样不收,这里也不入队)。 */
|
|
265
|
+
_batchKeyOf(urls, L) {
|
|
266
|
+
const maxTiles = tilesPerBatchOf(L);
|
|
267
|
+
if (!urls || urls.length === 0 || urls.length > maxTiles)
|
|
268
|
+
return "";
|
|
269
|
+
const u = urls.slice(0, maxTiles);
|
|
270
|
+
while (u.length < maxTiles)
|
|
271
|
+
u.push("");
|
|
272
|
+
return rtStitchBatchOf(u);
|
|
273
|
+
}
|
|
274
|
+
/** 这一组的每一格要么已经画进 RT,要么根本没有地址 / 地址已判死 ⇒ 该下的都下完了(队列出清判据)。 */
|
|
275
|
+
_isSettled(g) {
|
|
276
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
277
|
+
const ids = g.slotTiles[slot];
|
|
278
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
279
|
+
if (g.drawn[slot] & (1 << cell))
|
|
280
|
+
continue;
|
|
281
|
+
const url = g.urls[ids[cell]];
|
|
282
|
+
if (!url || this._dead.has(url))
|
|
283
|
+
continue;
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
269
288
|
}
|
|
270
289
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
271
290
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
@@ -318,13 +337,16 @@ export class PanoTileStitcher {
|
|
|
318
337
|
this._drawPass(renderer);
|
|
319
338
|
this._uploadPass(renderer);
|
|
320
339
|
}
|
|
321
|
-
/**
|
|
340
|
+
/** 释放一整组(2 张 RT + 该组还没画掉的瓦片纹理);队列里那一记任务跟着撤(组没了还排着 = 队首永远等不到落定) */
|
|
322
341
|
disposeGroup(batch) {
|
|
323
342
|
const g = this._groups.get(batch);
|
|
324
343
|
if (!g)
|
|
325
344
|
return;
|
|
326
345
|
this._dropGroupGpu(g);
|
|
327
346
|
this._groups.delete(batch);
|
|
347
|
+
const qi = this._queue.indexOf(batch);
|
|
348
|
+
if (qi >= 0)
|
|
349
|
+
this._queue.splice(qi, 1);
|
|
328
350
|
this._stamp++;
|
|
329
351
|
}
|
|
330
352
|
/** 释放全部(持有者 `dispose()` 时调用);共享的那套 quad/材质/空白纹理一并退掉 */
|
|
@@ -346,38 +368,58 @@ export class PanoTileStitcher {
|
|
|
346
368
|
_now() {
|
|
347
369
|
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
348
370
|
}
|
|
349
|
-
/** 工序 1:按「槽 0 格 0 →
|
|
350
|
-
*
|
|
371
|
+
/** 工序 1:按「槽 0 格 0 → …」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 / 下载中 ≤8 /
|
|
372
|
+
* 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。
|
|
373
|
+
* ⭐ 2026-10-30「加载任务队列」:队列非空时**只喂队首那一组**,它落定(`_isSettled`)就出队、下一帧轮到下一个;
|
|
374
|
+
* ⇒ 存档里第 1 颗全景独享额度下满,才轮到第 2 颗,之后才轮到名册按距离点到、排在队尾等着的那些全景。
|
|
375
|
+
* 队列为空 ⇒ 退回原来的在册顺序逐组补料(图片平面那一路不入队,照旧按在册顺序)。 */
|
|
351
376
|
_pumpDownloads() {
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
while (this._queue.length > 0) {
|
|
378
|
+
const head = this._groups.get(this._queue[0]);
|
|
379
|
+
if (head && !this._isSettled(head))
|
|
380
|
+
break;
|
|
381
|
+
this._queue.shift();
|
|
382
|
+
}
|
|
383
|
+
const ctx = { budget: PANO_STITCH_DOWNLOADS_PER_FRAME, inflight: 0, decoded: 0 };
|
|
354
384
|
for (const rec of this._texs.values()) {
|
|
355
385
|
if (rec.tex)
|
|
356
|
-
decoded++;
|
|
386
|
+
ctx.decoded++;
|
|
357
387
|
else
|
|
358
|
-
inflight++;
|
|
388
|
+
ctx.inflight++;
|
|
389
|
+
}
|
|
390
|
+
if (this._queue.length > 0) {
|
|
391
|
+
const head = this._groups.get(this._queue[0]);
|
|
392
|
+
if (head)
|
|
393
|
+
this._issueCells(head, ctx);
|
|
394
|
+
return;
|
|
359
395
|
}
|
|
360
|
-
let budget = PANO_STITCH_DOWNLOADS_PER_FRAME;
|
|
361
396
|
for (const g of this._groups.values()) {
|
|
362
|
-
if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
|
|
397
|
+
if (ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
|
|
363
398
|
return;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
399
|
+
if (!this._issueCells(g, ctx))
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** 某一组的取料循环:把「还没画、有地址、没判死、没发起过」的格子交给 `_loadTile`。
|
|
404
|
+
* 返回 false = 三道闸里有一道合上了 ⇒ 本帧别再往下看(`ctx` 是连扣的,跨组也接着算)。 */
|
|
405
|
+
_issueCells(g, ctx) {
|
|
406
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
407
|
+
const ids = g.slotTiles[slot];
|
|
408
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
409
|
+
if (g.drawn[slot] & (1 << cell))
|
|
410
|
+
continue;
|
|
411
|
+
const url = g.urls[ids[cell]];
|
|
412
|
+
if (!url || this._dead.has(url))
|
|
413
|
+
continue;
|
|
414
|
+
if (this._texs.has(url))
|
|
415
|
+
continue;
|
|
416
|
+
this._loadTile(url);
|
|
417
|
+
ctx.inflight++;
|
|
418
|
+
if (--ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
|
|
419
|
+
return false;
|
|
379
420
|
}
|
|
380
421
|
}
|
|
422
|
+
return true;
|
|
381
423
|
}
|
|
382
424
|
/** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
|
|
383
425
|
* ⛔ 准入只看一件事:`uploaded`。没上传过的纹理当场画 = `texImage2D` 藏进 `render()` 里
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
*
|
|
42
42
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
43
43
|
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
44
|
+
* ⭐ 2026-10-30 需求「加载任务队列」:`_queue` 非空时**只喂队首那一组**,那一组落定才出队开下一组
|
|
45
|
+
* ⇒ 存档前 2 颗全景是一颗一颗下的(第 1 颗下满才轮到第 2 颗),名册按距离点到的排在后面等。
|
|
44
46
|
* 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
|
|
45
47
|
* 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
|
|
46
48
|
* 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
|
|
@@ -239,10 +241,21 @@ export interface PanoStitcher {
|
|
|
239
241
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
240
242
|
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 8×4 / 2 槽 / 6144×6144,2026-10-27 需求);
|
|
241
243
|
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
ensure(urls: string[], layout?: TileLayout
|
|
244
|
+
* ⚠️ 2026-10-30「加载任务队列」:本方法只负责**入册**(新组追加到在册顺序末尾 ⇒ 排在队列里等),
|
|
245
|
+
* 点名优先的那一路换成交换 `prioritize()`(10-29 那版挂在第三个参 `front` 上的「挪到队首」已并进那里,
|
|
246
|
+
* ⛔ 一处排队只有 `prioritize` 一张尺)。 */
|
|
247
|
+
ensure(urls: string[], layout?: TileLayout): void;
|
|
248
|
+
/**
|
|
249
|
+
* 把某批瓦片登记进**加载任务队列**(2026-10-30 需求,逐字:「依次把前 2 个全景的顺序把瓦片加载任务添加到
|
|
250
|
+
* 任务队列里面,第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;后续按距离需要哪个全景
|
|
251
|
+
* 的时候同样添加到队列后面等待加载」)。
|
|
252
|
+
* 语义 = 先 `ensure` 入册(不在册就建组),再把批次键登记进队列**末尾**(已在队列里就不再挪,所以先登记的排前面)。
|
|
253
|
+
* 排队规则见 `_pumpDownloads`:**队首那组落定(32 格全画进 RT,或该格本就缺地址/判死)之前,队列里后面的组
|
|
254
|
+
* 和一切没登记的名册组一片都不发** ⇒ 第 1 颗全景独享全部下载额度、最快出全图。
|
|
255
|
+
* ⚠️ 消费方每帧点一次(`MktMesh.prefetchHeadPanoramas`):预览每开一次都换渲染器,`_switchRenderer` 会把在册组
|
|
256
|
+
* 整份作废 ⇒ 队列跟着清空,下一帧重新登记才能把顺序抢回来(只在 `importData` 里点一次等于白点)。
|
|
257
|
+
*/
|
|
258
|
+
prioritize(urls: string[], layout?: TileLayout): void;
|
|
246
259
|
/**
|
|
247
260
|
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
248
261
|
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
@@ -272,6 +285,10 @@ export interface PanoStitcher {
|
|
|
272
285
|
export class PanoTileStitcher implements PanoStitcher {
|
|
273
286
|
/** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
|
|
274
287
|
private _groups: Map<string, StitchGroup> = new Map();
|
|
288
|
+
/** 加载任务队列(2026-10-30 需求):批次键的**串行带**,队首那组落定才开下一组。
|
|
289
|
+
* 由 `prioritize` 登记(存档里前 2 颗全景),`_pumpDownloads` 每帧出清;`_switchRenderer`/`dispose`/`disposeGroup`
|
|
290
|
+
* 跟着组一起拆。⛔ 这是「谁先下载」的唯一一把尺 —— 在册顺序(`_groups` 的 Map 序)只决定「同不带队列时」的补料次序。 */
|
|
291
|
+
private _queue: string[] = [];
|
|
275
292
|
/** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
|
|
276
293
|
private _texs: Map<string, TileRec> = new Map();
|
|
277
294
|
/** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
|
|
@@ -329,17 +346,14 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
329
346
|
* ⚠️ 唯一按张数**能混进来**的是 10-06 那版同为 32 片的老档(切片顺序与槽分组两版一致 ⇒ 不会错位),
|
|
330
347
|
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
331
348
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
332
|
-
ensure(urls: string[], layout?: TileLayout
|
|
349
|
+
ensure(urls: string[], layout?: TileLayout): void {
|
|
333
350
|
const L = layout ?? PANO_TILE_LAYOUT;
|
|
334
351
|
const maxTiles = tilesPerBatchOf(L);
|
|
335
352
|
if (!urls || urls.length === 0 || urls.length > maxTiles) return;
|
|
353
|
+
const batch = this._batchKeyOf(urls, L);
|
|
354
|
+
if (this._groups.has(batch)) return;
|
|
336
355
|
const u = urls.slice();
|
|
337
356
|
while (u.length < maxTiles) u.push("");
|
|
338
|
-
const batch = rtStitchBatchOf(u);
|
|
339
|
-
if (this._groups.has(batch)) {
|
|
340
|
-
if (front) this._toFront(batch);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
357
|
const slotCount = L.slotCount;
|
|
344
358
|
const cellsPerSlot = cellsPerSlotOf(L);
|
|
345
359
|
this._groups.set(batch, {
|
|
@@ -353,21 +367,39 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
353
367
|
drawnCount: Array.from({ length: slotCount }, () => 0),
|
|
354
368
|
pending: slotCount,
|
|
355
369
|
});
|
|
356
|
-
if (front) this._toFront(batch);
|
|
357
370
|
this._stamp++;
|
|
358
371
|
}
|
|
359
372
|
|
|
360
|
-
/**
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
373
|
+
/** 登记进加载任务队列(语义与排队规则见接口上那一段)。已在队列里 ⇒ 不挪(先登记的仍在前)。 */
|
|
374
|
+
prioritize(urls: string[], layout?: TileLayout): void {
|
|
375
|
+
this.ensure(urls, layout);
|
|
376
|
+
const batch = this._batchKeyOf(urls, layout ?? PANO_TILE_LAYOUT);
|
|
377
|
+
if (!batch || !this._groups.has(batch) || this._queue.includes(batch)) return;
|
|
378
|
+
this._queue.push(batch);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** 地址数组 → 批次键(补齐到满批再取首条非空,与 `ensure` 里同一条尺 ⇒ 两处不会各算各的)。
|
|
382
|
+
* 超限或全空 ⇒ 空串(`ensure` 那一步同样不收,这里也不入队)。 */
|
|
383
|
+
private _batchKeyOf(urls: string[], L: TileLayout): string {
|
|
384
|
+
const maxTiles = tilesPerBatchOf(L);
|
|
385
|
+
if (!urls || urls.length === 0 || urls.length > maxTiles) return "";
|
|
386
|
+
const u = urls.slice(0, maxTiles);
|
|
387
|
+
while (u.length < maxTiles) u.push("");
|
|
388
|
+
return rtStitchBatchOf(u);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** 这一组的每一格要么已经画进 RT,要么根本没有地址 / 地址已判死 ⇒ 该下的都下完了(队列出清判据)。 */
|
|
392
|
+
private _isSettled(g: StitchGroup): boolean {
|
|
393
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
394
|
+
const ids = g.slotTiles[slot];
|
|
395
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
396
|
+
if (g.drawn[slot] & (1 << cell)) continue;
|
|
397
|
+
const url = g.urls[ids[cell]];
|
|
398
|
+
if (!url || this._dead.has(url)) continue;
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
371
403
|
}
|
|
372
404
|
|
|
373
405
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
@@ -421,12 +453,14 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
421
453
|
this._uploadPass(renderer);
|
|
422
454
|
}
|
|
423
455
|
|
|
424
|
-
/**
|
|
456
|
+
/** 释放一整组(2 张 RT + 该组还没画掉的瓦片纹理);队列里那一记任务跟着撤(组没了还排着 = 队首永远等不到落定) */
|
|
425
457
|
disposeGroup(batch: string): void {
|
|
426
458
|
const g = this._groups.get(batch);
|
|
427
459
|
if (!g) return;
|
|
428
460
|
this._dropGroupGpu(g);
|
|
429
461
|
this._groups.delete(batch);
|
|
462
|
+
const qi = this._queue.indexOf(batch);
|
|
463
|
+
if (qi >= 0) this._queue.splice(qi, 1);
|
|
430
464
|
this._stamp++;
|
|
431
465
|
}
|
|
432
466
|
|
|
@@ -451,31 +485,49 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
451
485
|
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
452
486
|
}
|
|
453
487
|
|
|
454
|
-
/** 工序 1:按「槽 0 格 0 →
|
|
455
|
-
*
|
|
488
|
+
/** 工序 1:按「槽 0 格 0 → …」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 / 下载中 ≤8 /
|
|
489
|
+
* 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。
|
|
490
|
+
* ⭐ 2026-10-30「加载任务队列」:队列非空时**只喂队首那一组**,它落定(`_isSettled`)就出队、下一帧轮到下一个;
|
|
491
|
+
* ⇒ 存档里第 1 颗全景独享额度下满,才轮到第 2 颗,之后才轮到名册按距离点到、排在队尾等着的那些全景。
|
|
492
|
+
* 队列为空 ⇒ 退回原来的在册顺序逐组补料(图片平面那一路不入队,照旧按在册顺序)。 */
|
|
456
493
|
private _pumpDownloads(): void {
|
|
457
|
-
|
|
458
|
-
|
|
494
|
+
while (this._queue.length > 0) {
|
|
495
|
+
const head = this._groups.get(this._queue[0]);
|
|
496
|
+
if (head && !this._isSettled(head)) break;
|
|
497
|
+
this._queue.shift();
|
|
498
|
+
}
|
|
499
|
+
const ctx = { budget: PANO_STITCH_DOWNLOADS_PER_FRAME, inflight: 0, decoded: 0 };
|
|
459
500
|
for (const rec of this._texs.values()) {
|
|
460
|
-
if (rec.tex) decoded++;
|
|
461
|
-
else inflight++;
|
|
501
|
+
if (rec.tex) ctx.decoded++;
|
|
502
|
+
else ctx.inflight++;
|
|
503
|
+
}
|
|
504
|
+
if (this._queue.length > 0) {
|
|
505
|
+
const head = this._groups.get(this._queue[0]);
|
|
506
|
+
if (head) this._issueCells(head, ctx);
|
|
507
|
+
return;
|
|
462
508
|
}
|
|
463
|
-
let budget = PANO_STITCH_DOWNLOADS_PER_FRAME;
|
|
464
509
|
for (const g of this._groups.values()) {
|
|
465
|
-
if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
510
|
+
if (ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
|
|
511
|
+
if (!this._issueCells(g, ctx)) return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** 某一组的取料循环:把「还没画、有地址、没判死、没发起过」的格子交给 `_loadTile`。
|
|
516
|
+
* 返回 false = 三道闸里有一道合上了 ⇒ 本帧别再往下看(`ctx` 是连扣的,跨组也接着算)。 */
|
|
517
|
+
private _issueCells(g: StitchGroup, ctx: { budget: number; inflight: number; decoded: number }): boolean {
|
|
518
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
519
|
+
const ids = g.slotTiles[slot];
|
|
520
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
521
|
+
if (g.drawn[slot] & (1 << cell)) continue;
|
|
522
|
+
const url = g.urls[ids[cell]];
|
|
523
|
+
if (!url || this._dead.has(url)) continue;
|
|
524
|
+
if (this._texs.has(url)) continue;
|
|
525
|
+
this._loadTile(url);
|
|
526
|
+
ctx.inflight++;
|
|
527
|
+
if (--ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return false;
|
|
477
528
|
}
|
|
478
529
|
}
|
|
530
|
+
return true;
|
|
479
531
|
}
|
|
480
532
|
|
|
481
533
|
/** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
|
package/MktMesh/types.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Matrix4 } from "three";
|
|
2
2
|
import type { ImagePlaneData } from "./imagePlane";
|
|
3
3
|
import type { ModelSlotData } from "./modelSlot";
|
|
4
|
-
import type { GlassPaneData } from "./glassPane";
|
|
5
4
|
/**
|
|
6
5
|
* 单个纹理的运行时信息:url + 矩阵(位置 + 旋转,缩放忽略)。
|
|
7
6
|
* ⚠️ 这个矩阵记在 MktMesh **自身局部(制图)空间**,与 `wallPoints` 同一空间 —— 不是世界矩阵:
|
|
@@ -88,9 +87,4 @@ export interface MktMeshData {
|
|
|
88
87
|
images?: ImagePlaneData[];
|
|
89
88
|
/** 模型槽层(同一道旨):`url` 是 .glb 的相对地址;空串 = 还没上传 ⇒ 显示占位小立方体 */
|
|
90
89
|
models?: ModelSlotData[];
|
|
91
|
-
/**
|
|
92
|
-
* 玻璃面层(2026-10-28 需求「正方的面」):正方形、双面、transmission 折射,反射环境取运行时第一个全景。
|
|
93
|
-
* ⛔ 这一层不落任何素材地址 ⇒ 既不过 `stripAssetHost` 也不过 `absAssetUrl`,只有位置/旋转/边长。
|
|
94
|
-
*/
|
|
95
|
-
glass?: GlassPaneData[];
|
|
96
90
|
}
|
package/MktMesh/types.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import type { Matrix4 } from "three";
|
|
9
9
|
import type { ImagePlaneData } from "./imagePlane";
|
|
10
10
|
import type { ModelSlotData } from "./modelSlot";
|
|
11
|
-
import type { GlassPaneData } from "./glassPane";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* 单个纹理的运行时信息:url + 矩阵(位置 + 旋转,缩放忽略)。
|
|
@@ -100,9 +99,4 @@ export interface MktMeshData {
|
|
|
100
99
|
images?: ImagePlaneData[];
|
|
101
100
|
/** 模型槽层(同一道旨):`url` 是 .glb 的相对地址;空串 = 还没上传 ⇒ 显示占位小立方体 */
|
|
102
101
|
models?: ModelSlotData[];
|
|
103
|
-
/**
|
|
104
|
-
* 玻璃面层(2026-10-28 需求「正方的面」):正方形、双面、transmission 折射,反射环境取运行时第一个全景。
|
|
105
|
-
* ⛔ 这一层不落任何素材地址 ⇒ 既不过 `stripAssetHost` 也不过 `absAssetUrl`,只有位置/旋转/边长。
|
|
106
|
-
*/
|
|
107
|
-
glass?: GlassPaneData[];
|
|
108
102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-base",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.195",
|
|
4
4
|
"description": "A Three.js extension library providing common 3D components, animations, materials, and utilities",
|
|
5
5
|
"author": "xiaosihan",
|
|
6
6
|
"publishConfig": {
|
|
@@ -47,11 +47,6 @@
|
|
|
47
47
|
"import": "./MktMesh/modelSlot.js",
|
|
48
48
|
"require": "./MktMesh/modelSlot.js"
|
|
49
49
|
},
|
|
50
|
-
"./MktMesh/glassPane": {
|
|
51
|
-
"types": "./MktMesh/glassPane.d.ts",
|
|
52
|
-
"import": "./MktMesh/glassPane.js",
|
|
53
|
-
"require": "./MktMesh/glassPane.js"
|
|
54
|
-
},
|
|
55
50
|
"./MktMesh/boxLine2": {
|
|
56
51
|
"types": "./MktMesh/boxLine2.d.ts",
|
|
57
52
|
"import": "./MktMesh/boxLine2.js",
|
package/MktMesh/glassPane.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Mesh, MeshPhysicalMaterial, PlaneGeometry, Texture } from "three";
|
|
2
|
-
/** 玻璃面的持久化数据(进 `MktMesh.exportData` 的 `glass` 键 / `importData` 还原) */
|
|
3
|
-
export interface GlassPaneData {
|
|
4
|
-
name?: string;
|
|
5
|
-
position: {
|
|
6
|
-
x: number;
|
|
7
|
-
y: number;
|
|
8
|
-
z: number;
|
|
9
|
-
};
|
|
10
|
-
rotation: {
|
|
11
|
-
x: number;
|
|
12
|
-
y: number;
|
|
13
|
-
z: number;
|
|
14
|
-
};
|
|
15
|
-
/** 正方形边长(米):宽 = 高 = 本值,⛔ 不拆成两档 */
|
|
16
|
-
size: number;
|
|
17
|
-
}
|
|
18
|
-
export declare class GlassPane extends Mesh<PlaneGeometry, MeshPhysicalMaterial> {
|
|
19
|
-
private _originalColor;
|
|
20
|
-
/** 反射环境纹理的身份(由 `MktMesh` 注入,本类只持有引用、⛔ 从不 dispose 它 —— 那张按地址缓存在库里) */
|
|
21
|
-
private _envTex;
|
|
22
|
-
constructor(name?: string);
|
|
23
|
-
get paneSize(): number;
|
|
24
|
-
/** 换正方形边长(宽高等比跟着走:几何只有这一把尺) */
|
|
25
|
-
setPaneSize(size: number): void;
|
|
26
|
-
/**
|
|
27
|
-
* 绑/摘反射环境纹理(`null` = 还没有可用的环境源)。
|
|
28
|
-
* ⚠️ 判等在前:`material.needsUpdate = true` 会涨材质版本 ⇒ 程序要重编译。库里给同一批玻璃面注入的是
|
|
29
|
-
* **同一个 Texture 对象**(反射源只下载一次),逐颗重复赋同一张时必须早退,不然就是 N 次无谓重编译。
|
|
30
|
-
* ⚠️ 传进来的纹理必须已标 `EquirectangularReflectionMapping`:three 的 `WebGLCubeUVMaps` 只认那个标记才会
|
|
31
|
-
* 自动把它卷积成立方 UV(PMREM);直接给一张普通 UV 纹理(例如全景拼合槽的那两张 RT)采出来是花屏。
|
|
32
|
-
*/
|
|
33
|
-
setEnvironmentTexture(tex: Texture | null): void;
|
|
34
|
-
highlight(): void;
|
|
35
|
-
restoreColor(): void;
|
|
36
|
-
toData(): GlassPaneData;
|
|
37
|
-
applyData(d: GlassPaneData): void;
|
|
38
|
-
/** 只释放自己的几何与材质;反射纹理归 `MktMesh` 管(多颗共用一张)。 */
|
|
39
|
-
dispose(): void;
|
|
40
|
-
}
|
|
41
|
-
export declare const GLASS_PANE_DEFAULTS: {
|
|
42
|
-
size: number;
|
|
43
|
-
};
|