three-base 2.0.198 → 2.0.199
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 +32 -2
- package/MktMesh/MktMesh.js +62 -5
- package/MktMesh/MktMesh.ts +63 -5
- package/MktMesh/mktResManager.d.ts +77 -0
- package/MktMesh/mktResManager.js +89 -0
- package/MktMesh/mktResManager.ts +130 -0
- package/package.json +1 -1
package/MktMesh/MktMesh.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type PanoStitcher } from "./panoTileStitcher";
|
|
|
5
5
|
import type { MktMeshData, MktTextureInfo, WallPoint, WallSide } from "./types";
|
|
6
6
|
import { ImagePlane, type ImagePlaneData } from "./imagePlane";
|
|
7
7
|
import { ModelSlot, type ModelSlotData } from "./modelSlot";
|
|
8
|
+
import { MktResourceManager } from "./mktResManager";
|
|
8
9
|
export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
9
10
|
/** 纹理列表(每个纹理的 url + 世界矩阵) */
|
|
10
11
|
textures: MktTextureInfo[];
|
|
@@ -122,8 +123,10 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
122
123
|
* 必须与基类喂纹理处用同一个值,否则标记与地址不同源 ⇒ 整图被按 1/2 横向铺满球面(10-06 晚起纵向不分块,四槽时代是 1/4)。 */
|
|
123
124
|
get panoTexMode(): PanoTexMode;
|
|
124
125
|
/** 优先加载的全景个数(存档 `textures` 的前 n 项):**-1 = 全部、0 = 一个都不提前、n = 前 n 个**(2026-11-05 需求)。
|
|
125
|
-
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(=
|
|
126
|
-
*
|
|
126
|
+
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(= -1,同日需求「不管有没有显示出来的全景图都应该默认全部加载」);
|
|
127
|
+
* 宿主想收窄,在渲染循环跑起来之前 `setPanoPriorityCount(4)` 一行即可
|
|
128
|
+
* (与 `_ossSize`、`_panoTexMode` 同一套注入路径 ⇒ 编辑器与预览各自的配置各自推下来)。
|
|
129
|
+
* ⚠️ 这一档只定**下载与拼合的次序**;隐藏期里谁来推那一拍 = `preRender(renderer)`(需求第 1、2 点)。 */
|
|
127
130
|
private _panoPriorityCount;
|
|
128
131
|
/** 当前优先加载个数(只读,回显用)。 */
|
|
129
132
|
get panoPriorityCount(): number;
|
|
@@ -168,6 +171,14 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
168
171
|
* 否则它会在轮到自身那一帧被写回可见(`boolean` 只有一个位,放行那里分不出这笔是谁写的)。
|
|
169
172
|
*/
|
|
170
173
|
private _revealPending;
|
|
174
|
+
/**
|
|
175
|
+
* ⭐ 2026-11-05 需求「专门写一个资源管理器来管理,每个 mktmesh 都有自己的一个资源管理器」:本对象一份。
|
|
176
|
+
* 它不下载、不解码、不上传 ⇒ 全部经下面那几只手点本对象上**现成**的入口(拼合器 `pump` / `prefetchHeadPanoramas` /
|
|
177
|
+
* `feedImagePlaneTiles` / 隐藏期预热),只多管两件事:隐藏期由谁来推那一拍、以及「哪一档真画进过显存」这本账。
|
|
178
|
+
*/
|
|
179
|
+
private _res;
|
|
180
|
+
/** 本对象的资源管理器(只读)。宿主只管调 `preRender`,需要看账本时才用它。 */
|
|
181
|
+
get resManager(): MktResourceManager;
|
|
171
182
|
/**
|
|
172
183
|
* 隐藏期 GPU 预热的在册清单(元素 = 一个「会进渲染列表的物件」,即它的材质组合 × 几何)。
|
|
173
184
|
* null = 没在预热(未启动 / 已跑完 / `stopGpuPrewarm` / `dispose` 四种都是它)。
|
|
@@ -424,8 +435,27 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
424
435
|
* 表空了它就是一句 `length` 判断,⛔ 不需要再像旧写法那样把钩子置 null(钩子上还挂着全景投影那三件事)。
|
|
425
436
|
* ⚠️ 这里写的 `visible` 只针对**自己压进表的那几颗** ⇒ 表外的对象(单颗新增、已 dispose、被 `releaseReveal`
|
|
426
437
|
* 摘牌的)一律不碰;要在放行前接管某颗,调 `releaseReveal(o)`。
|
|
438
|
+
* ⭐ 2026-11-05 需求第 4 点「只已经预载入的资源才显示出来」:内容落定(`o.loaded`)之后还要问一句资源管理器
|
|
439
|
+
* 「这一档画进显存了吗」—— 只有宿主调过 `preRender` 且预热还挂着牌时才拦,摘牌 / 没调过 ⇒ 一律放行(⛔ 不许拦出永久黑屏)。
|
|
427
440
|
*/
|
|
428
441
|
tickProgressiveReveal(): void;
|
|
442
|
+
/**
|
|
443
|
+
* ⭐ 2026-11-05 需求第 1 点逐字:「需要提供一个 `preRender` 方法, 传入一个渲染器对象, 然后在这个方法里面使用
|
|
444
|
+
* 渲染来预载入已经准备好的资源到 GPU」。隐藏期(`visible = false`)里 three 遍历第一句就把本对象连同子树跳过
|
|
445
|
+
* ⇒ `onBeforeRender` 一次都不触发,全景瓦片不下载不拼合、平面不绑、GLB 与墙的 program 不编不传;
|
|
446
|
+
* 本方法就是那句「藏起来的时候替它跑一遍渲染循环里的推进」,交进来的渲染器同时供拼合器与预热用。
|
|
447
|
+
*
|
|
448
|
+
* 一次点齐三族(次序固定,与 `onBeforeRender` 里同一相对位置):
|
|
449
|
+
* ① 全景 —— `stitcher.pump(renderer)` 逐 tick 走「补下载 → 画已进显存的格 → 传一张新的」,
|
|
450
|
+
* 额度由 `panoPriorityCount` 点的批排在前(默认 -1 = 整份存档,需求第 2 点);
|
|
451
|
+
* ② 平面图 —— `feedImagePlaneTiles()` 一次把整层地址交给下载、绑定一帧一颗(需求第 3 点:这两族本来就全量发起);
|
|
452
|
+
* ③ GLB / 平面 / 墙 —— 下面的 `startGpuPrewarm` 那一族逐 tick 画一件进 1×1 目标(program 编译 + 顶点缓冲首传)。
|
|
453
|
+
*
|
|
454
|
+
* 幂等 ⇒ 隐藏期里每帧点一次、或只在载入后点一次都行;已排进队列的那条链不会被重复起。
|
|
455
|
+
* 本对象一旦 `visible`(进厅),链自己停 ⇒ 之后交回 `onBeforeRender` 每帧那一路,⛔ 不抢同一帧的额度。
|
|
456
|
+
* ⛔ 不调本方法的宿主(发布页直接亮墙、编辑器画布)行为与改造前逐字一致:显示闸只在 `preRender` 点过之后才生效。
|
|
457
|
+
*/
|
|
458
|
+
preRender(renderer: WebGLRenderer): void;
|
|
429
459
|
/**
|
|
430
460
|
* 开始隐藏期预热:把「进厅第一帧要现编的那些 program + 要第一次上传的那些几何/纹理」提前跑掉,一帧只放**一件**。
|
|
431
461
|
*
|
package/MktMesh/MktMesh.js
CHANGED
|
@@ -51,11 +51,16 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
|
|
|
51
51
|
import { ImagePlane } from "./imagePlane";
|
|
52
52
|
import { ModelSlot, glbFileName } from "./modelSlot";
|
|
53
53
|
import { ImagePlaneTileLoader } from "./imageTileLoader";
|
|
54
|
+
// ⭐ 用户需求 2026-11-05「专门写一个资源管理器来管理, 每个 mktmesh 都有自己的一个资源管理器来管理」:
|
|
55
|
+
// 隐藏期(`visible = false` ⇒ `onBeforeRender` 一次都不触发)里那三族资源的**驱动次序**与**是否已进显存**这本账。
|
|
56
|
+
import { MktResourceManager } from "./mktResManager";
|
|
54
57
|
/** 载入 JSON 时**优先**加载几个全景的素材的**默认档**(数值只写这一处)。
|
|
55
58
|
* 口径 = 存档 `textures` 数组的前 n 项(n = -1 视为整份全加载);其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。
|
|
56
59
|
* 2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」立的是 2;
|
|
57
|
-
* 2026-11-05 需求逐字「现在要默认把全景全部都要默认加载, 现在需要通过配置来设置, 提前加载多少个全景 -1 就是全加载 0就是不加载 1就是加载个 2就提前加载2个 一次类推, 默认加载前4个」⇒
|
|
58
|
-
|
|
60
|
+
* 2026-11-05 需求逐字「现在要默认把全景全部都要默认加载, 现在需要通过配置来设置, 提前加载多少个全景 -1 就是全加载 0就是不加载 1就是加载个 2就提前加载2个 一次类推, 默认加载前4个」⇒ 换成可配置的 4;
|
|
61
|
+
* 同日需求逐字「panoPriorityCount 设置为 -1, 不管有没有显示出来的全景图都应该默认全部加载, 等待渲染器的预载入」⇒ 默认档落 **-1(整份全加载)**。
|
|
62
|
+
* ⚠️ 这一档管的是**下载与拼合的次序**;「亮出来之前先把它推进显存」是 `preRender(renderer)` 那一手(见 `./mktResManager`)。 */
|
|
63
|
+
const PANO_IMPORT_PRIORITY_DEFAULT = -1;
|
|
59
64
|
/** 隐藏期预热那一次绘制用的镜头(模块级共用:它只是个「有合法的投影矩阵可读」的载体,永不动 ⇒ ⛔ 不必每实例造一份)。 */
|
|
60
65
|
const GPU_WARM_CAMERA = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
61
66
|
/**
|
|
@@ -196,8 +201,10 @@ export default class MktMesh extends Mesh {
|
|
|
196
201
|
return this._panoTexMode;
|
|
197
202
|
}
|
|
198
203
|
/** 优先加载的全景个数(存档 `textures` 的前 n 项):**-1 = 全部、0 = 一个都不提前、n = 前 n 个**(2026-11-05 需求)。
|
|
199
|
-
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(=
|
|
200
|
-
*
|
|
204
|
+
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(= -1,同日需求「不管有没有显示出来的全景图都应该默认全部加载」);
|
|
205
|
+
* 宿主想收窄,在渲染循环跑起来之前 `setPanoPriorityCount(4)` 一行即可
|
|
206
|
+
* (与 `_ossSize`、`_panoTexMode` 同一套注入路径 ⇒ 编辑器与预览各自的配置各自推下来)。
|
|
207
|
+
* ⚠️ 这一档只定**下载与拼合的次序**;隐藏期里谁来推那一拍 = `preRender(renderer)`(需求第 1、2 点)。 */
|
|
201
208
|
_panoPriorityCount = PANO_IMPORT_PRIORITY_DEFAULT;
|
|
202
209
|
/** 当前优先加载个数(只读,回显用)。 */
|
|
203
210
|
get panoPriorityCount() {
|
|
@@ -245,6 +252,25 @@ export default class MktMesh extends Mesh {
|
|
|
245
252
|
* 否则它会在轮到自身那一帧被写回可见(`boolean` 只有一个位,放行那里分不出这笔是谁写的)。
|
|
246
253
|
*/
|
|
247
254
|
_revealPending = [];
|
|
255
|
+
/**
|
|
256
|
+
* ⭐ 2026-11-05 需求「专门写一个资源管理器来管理,每个 mktmesh 都有自己的一个资源管理器」:本对象一份。
|
|
257
|
+
* 它不下载、不解码、不上传 ⇒ 全部经下面那几只手点本对象上**现成**的入口(拼合器 `pump` / `prefetchHeadPanoramas` /
|
|
258
|
+
* `feedImagePlaneTiles` / 隐藏期预热),只多管两件事:隐藏期由谁来推那一拍、以及「哪一档真画进过显存」这本账。
|
|
259
|
+
*/
|
|
260
|
+
_res = new MktResourceManager({
|
|
261
|
+
prewarm: (renderer) => this.startGpuPrewarm(renderer),
|
|
262
|
+
stopPrewarm: () => this.stopGpuPrewarm(),
|
|
263
|
+
// 挂着牌 = 还没摘牌且还没亮出来 ⇒ 与 `_topUpGpuPrewarm` 用的同一颗尺(⛔ 不自立第二道闸)
|
|
264
|
+
prewarming: () => this._warmSeen !== null && !this.visible,
|
|
265
|
+
shown: () => this.visible,
|
|
266
|
+
pumpPano: (renderer) => this._stitcher.pump(renderer),
|
|
267
|
+
prefetchPano: () => this.prefetchHeadPanoramas(),
|
|
268
|
+
feedPlanes: () => this.feedImagePlaneTiles(),
|
|
269
|
+
});
|
|
270
|
+
/** 本对象的资源管理器(只读)。宿主只管调 `preRender`,需要看账本时才用它。 */
|
|
271
|
+
get resManager() {
|
|
272
|
+
return this._res;
|
|
273
|
+
}
|
|
248
274
|
/**
|
|
249
275
|
* 隐藏期 GPU 预热的在册清单(元素 = 一个「会进渲染列表的物件」,即它的材质组合 × 几何)。
|
|
250
276
|
* null = 没在预热(未启动 / 已跑完 / `stopGpuPrewarm` / `dispose` 四种都是它)。
|
|
@@ -294,7 +320,8 @@ export default class MktMesh extends Mesh {
|
|
|
294
320
|
// `updateTexture`(它里面那记 `pump` 是「第一次拿到 renderer ⇒ 在册组整份作废」的现场)之后、
|
|
295
321
|
// `feedFaceMapping`(名册逐槽入册)之前 —— 顺序倒过来就是「名册在前、我们垫底」= 静默失效。
|
|
296
322
|
// 编辑态那份在 `MktDevMesh` 的宿主循环里照同一相对位置点(⛔ 别在这儿加第三处)。
|
|
297
|
-
// 2026-11-05 需求:这一批点几颗改由 `panoPriorityCount` 定(默认
|
|
323
|
+
// 2026-11-05 需求:这一批点几颗改由 `panoPriorityCount` 定(默认 -1 = 存档全部)⇒ 本句不动;
|
|
324
|
+
// 同一批在**隐藏期**由 `preRender(renderer)` 逐 tick 推(本钩子在那时一次都不触发)。
|
|
298
325
|
this.prefetchHeadPanoramas();
|
|
299
326
|
this.feedFaceMapping();
|
|
300
327
|
this.tickProgressiveReveal();
|
|
@@ -1328,6 +1355,8 @@ export default class MktMesh extends Mesh {
|
|
|
1328
1355
|
* 表空了它就是一句 `length` 判断,⛔ 不需要再像旧写法那样把钩子置 null(钩子上还挂着全景投影那三件事)。
|
|
1329
1356
|
* ⚠️ 这里写的 `visible` 只针对**自己压进表的那几颗** ⇒ 表外的对象(单颗新增、已 dispose、被 `releaseReveal`
|
|
1330
1357
|
* 摘牌的)一律不碰;要在放行前接管某颗,调 `releaseReveal(o)`。
|
|
1358
|
+
* ⭐ 2026-11-05 需求第 4 点「只已经预载入的资源才显示出来」:内容落定(`o.loaded`)之后还要问一句资源管理器
|
|
1359
|
+
* 「这一档画进显存了吗」—— 只有宿主调过 `preRender` 且预热还挂着牌时才拦,摘牌 / 没调过 ⇒ 一律放行(⛔ 不许拦出永久黑屏)。
|
|
1331
1360
|
*/
|
|
1332
1361
|
tickProgressiveReveal() {
|
|
1333
1362
|
const q = this._revealPending;
|
|
@@ -1335,12 +1364,33 @@ export default class MktMesh extends Mesh {
|
|
|
1335
1364
|
const o = q[i];
|
|
1336
1365
|
if (!o.loaded)
|
|
1337
1366
|
continue; // 内容还在路上 ⇒ 这一颗继续压着,先看后面已经落定的
|
|
1367
|
+
if (this._res.holdsReveal(o))
|
|
1368
|
+
continue; // 内容到了但还没进显存 ⇒ 等 `preRender` 那一拍画过它
|
|
1338
1369
|
q.splice(i, 1);
|
|
1339
1370
|
o.visible = true;
|
|
1340
1371
|
return;
|
|
1341
1372
|
}
|
|
1342
1373
|
}
|
|
1343
1374
|
// ─────────────────── 隐藏期 GPU 预热(2026-11-01 需求:进厅那一下不要再现编着色器) ───────────────────
|
|
1375
|
+
/**
|
|
1376
|
+
* ⭐ 2026-11-05 需求第 1 点逐字:「需要提供一个 `preRender` 方法, 传入一个渲染器对象, 然后在这个方法里面使用
|
|
1377
|
+
* 渲染来预载入已经准备好的资源到 GPU」。隐藏期(`visible = false`)里 three 遍历第一句就把本对象连同子树跳过
|
|
1378
|
+
* ⇒ `onBeforeRender` 一次都不触发,全景瓦片不下载不拼合、平面不绑、GLB 与墙的 program 不编不传;
|
|
1379
|
+
* 本方法就是那句「藏起来的时候替它跑一遍渲染循环里的推进」,交进来的渲染器同时供拼合器与预热用。
|
|
1380
|
+
*
|
|
1381
|
+
* 一次点齐三族(次序固定,与 `onBeforeRender` 里同一相对位置):
|
|
1382
|
+
* ① 全景 —— `stitcher.pump(renderer)` 逐 tick 走「补下载 → 画已进显存的格 → 传一张新的」,
|
|
1383
|
+
* 额度由 `panoPriorityCount` 点的批排在前(默认 -1 = 整份存档,需求第 2 点);
|
|
1384
|
+
* ② 平面图 —— `feedImagePlaneTiles()` 一次把整层地址交给下载、绑定一帧一颗(需求第 3 点:这两族本来就全量发起);
|
|
1385
|
+
* ③ GLB / 平面 / 墙 —— 下面的 `startGpuPrewarm` 那一族逐 tick 画一件进 1×1 目标(program 编译 + 顶点缓冲首传)。
|
|
1386
|
+
*
|
|
1387
|
+
* 幂等 ⇒ 隐藏期里每帧点一次、或只在载入后点一次都行;已排进队列的那条链不会被重复起。
|
|
1388
|
+
* 本对象一旦 `visible`(进厅),链自己停 ⇒ 之后交回 `onBeforeRender` 每帧那一路,⛔ 不抢同一帧的额度。
|
|
1389
|
+
* ⛔ 不调本方法的宿主(发布页直接亮墙、编辑器画布)行为与改造前逐字一致:显示闸只在 `preRender` 点过之后才生效。
|
|
1390
|
+
*/
|
|
1391
|
+
preRender(renderer) {
|
|
1392
|
+
this._res.preRender(renderer);
|
|
1393
|
+
}
|
|
1344
1394
|
/**
|
|
1345
1395
|
* 开始隐藏期预热:把「进厅第一帧要现编的那些 program + 要第一次上传的那些几何/纹理」提前跑掉,一帧只放**一件**。
|
|
1346
1396
|
*
|
|
@@ -1442,6 +1492,9 @@ export default class MktMesh extends Mesh {
|
|
|
1442
1492
|
return;
|
|
1443
1493
|
seen.add(key);
|
|
1444
1494
|
items.push({ src: o, m: mesh.material, g });
|
|
1495
|
+
// ⭐ 2026-11-05 需求第 4 点:把「这一颗属于哪一档」交给资源管理器记账(键的算式仍只在这里这一处),
|
|
1496
|
+
// 等 `_runGpuWarmStep` 真画过它再翻成「已进显存」⇒ `tickProgressiveReveal` 才放行。
|
|
1497
|
+
this._res.noteWarmKey(o, key);
|
|
1445
1498
|
added++;
|
|
1446
1499
|
});
|
|
1447
1500
|
return added;
|
|
@@ -1492,6 +1545,9 @@ export default class MktMesh extends Mesh {
|
|
|
1492
1545
|
renderer.setRenderTarget(this._warmRt);
|
|
1493
1546
|
renderer.render(scene, GPU_WARM_CAMERA);
|
|
1494
1547
|
renderer.setRenderTarget(prevTarget);
|
|
1548
|
+
// ⭐ 2026-11-05 需求第 4 点:这一档刚刚真画进显存(program 编了、顶点缓冲与纹理首传也做了)⇒ 记上「已常驻」,
|
|
1549
|
+
// `tickProgressiveReveal` 那一拍才许把对应那颗亮出来(同一档的第二、第三颗跟着一起放行)。
|
|
1550
|
+
this._res.markWarmDrawn(it.src);
|
|
1495
1551
|
this._scheduleGpuWarmStep(renderer);
|
|
1496
1552
|
}
|
|
1497
1553
|
/**
|
|
@@ -1750,6 +1806,7 @@ export default class MktMesh extends Mesh {
|
|
|
1750
1806
|
this._revealPending = [];
|
|
1751
1807
|
// 隐藏期预热:清单、去重表与小屋子那套临时资源随本对象一起退场(那张 1×1 渲染目标是本类**唯一**自建的 RT)。
|
|
1752
1808
|
// 走 `stopGpuPrewarm` 而不是 `_finishGpuPrewarm`:本对象都要没了,去重表再留着就是泄漏。
|
|
1809
|
+
this._res.stop(); // 资源管理器:全景/平面那条链停、显示闸开、显存账作废(内部同样落到 `stopGpuPrewarm`)
|
|
1753
1810
|
this.stopGpuPrewarm();
|
|
1754
1811
|
this._stitcher.dispose();
|
|
1755
1812
|
this._glRenderer = null;
|
package/MktMesh/MktMesh.ts
CHANGED
|
@@ -77,12 +77,17 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
|
|
|
77
77
|
import { ImagePlane, type ImagePlaneData } from "./imagePlane";
|
|
78
78
|
import { ModelSlot, glbFileName, type ModelSlotData } from "./modelSlot";
|
|
79
79
|
import { ImagePlaneTileLoader } from "./imageTileLoader";
|
|
80
|
+
// ⭐ 用户需求 2026-11-05「专门写一个资源管理器来管理, 每个 mktmesh 都有自己的一个资源管理器来管理」:
|
|
81
|
+
// 隐藏期(`visible = false` ⇒ `onBeforeRender` 一次都不触发)里那三族资源的**驱动次序**与**是否已进显存**这本账。
|
|
82
|
+
import { MktResourceManager } from "./mktResManager";
|
|
80
83
|
|
|
81
84
|
/** 载入 JSON 时**优先**加载几个全景的素材的**默认档**(数值只写这一处)。
|
|
82
85
|
* 口径 = 存档 `textures` 数组的前 n 项(n = -1 视为整份全加载);其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。
|
|
83
86
|
* 2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」立的是 2;
|
|
84
|
-
* 2026-11-05 需求逐字「现在要默认把全景全部都要默认加载, 现在需要通过配置来设置, 提前加载多少个全景 -1 就是全加载 0就是不加载 1就是加载个 2就提前加载2个 一次类推, 默认加载前4个」⇒
|
|
85
|
-
|
|
87
|
+
* 2026-11-05 需求逐字「现在要默认把全景全部都要默认加载, 现在需要通过配置来设置, 提前加载多少个全景 -1 就是全加载 0就是不加载 1就是加载个 2就提前加载2个 一次类推, 默认加载前4个」⇒ 换成可配置的 4;
|
|
88
|
+
* 同日需求逐字「panoPriorityCount 设置为 -1, 不管有没有显示出来的全景图都应该默认全部加载, 等待渲染器的预载入」⇒ 默认档落 **-1(整份全加载)**。
|
|
89
|
+
* ⚠️ 这一档管的是**下载与拼合的次序**;「亮出来之前先把它推进显存」是 `preRender(renderer)` 那一手(见 `./mktResManager`)。 */
|
|
90
|
+
const PANO_IMPORT_PRIORITY_DEFAULT = -1;
|
|
86
91
|
|
|
87
92
|
/** 隐藏期预热那一次绘制用的镜头(模块级共用:它只是个「有合法的投影矩阵可读」的载体,永不动 ⇒ ⛔ 不必每实例造一份)。 */
|
|
88
93
|
const GPU_WARM_CAMERA = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
@@ -234,8 +239,10 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
234
239
|
}
|
|
235
240
|
|
|
236
241
|
/** 优先加载的全景个数(存档 `textures` 的前 n 项):**-1 = 全部、0 = 一个都不提前、n = 前 n 个**(2026-11-05 需求)。
|
|
237
|
-
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(=
|
|
238
|
-
*
|
|
242
|
+
* 默认取 `PANO_IMPORT_PRIORITY_DEFAULT`(= -1,同日需求「不管有没有显示出来的全景图都应该默认全部加载」);
|
|
243
|
+
* 宿主想收窄,在渲染循环跑起来之前 `setPanoPriorityCount(4)` 一行即可
|
|
244
|
+
* (与 `_ossSize`、`_panoTexMode` 同一套注入路径 ⇒ 编辑器与预览各自的配置各自推下来)。
|
|
245
|
+
* ⚠️ 这一档只定**下载与拼合的次序**;隐藏期里谁来推那一拍 = `preRender(renderer)`(需求第 1、2 点)。 */
|
|
239
246
|
private _panoPriorityCount: number = PANO_IMPORT_PRIORITY_DEFAULT;
|
|
240
247
|
/** 当前优先加载个数(只读,回显用)。 */
|
|
241
248
|
get panoPriorityCount(): number {
|
|
@@ -286,6 +293,26 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
286
293
|
*/
|
|
287
294
|
private _revealPending: Array<ImagePlane | ModelSlot> = [];
|
|
288
295
|
|
|
296
|
+
/**
|
|
297
|
+
* ⭐ 2026-11-05 需求「专门写一个资源管理器来管理,每个 mktmesh 都有自己的一个资源管理器」:本对象一份。
|
|
298
|
+
* 它不下载、不解码、不上传 ⇒ 全部经下面那几只手点本对象上**现成**的入口(拼合器 `pump` / `prefetchHeadPanoramas` /
|
|
299
|
+
* `feedImagePlaneTiles` / 隐藏期预热),只多管两件事:隐藏期由谁来推那一拍、以及「哪一档真画进过显存」这本账。
|
|
300
|
+
*/
|
|
301
|
+
private _res = new MktResourceManager({
|
|
302
|
+
prewarm: (renderer) => this.startGpuPrewarm(renderer),
|
|
303
|
+
stopPrewarm: () => this.stopGpuPrewarm(),
|
|
304
|
+
// 挂着牌 = 还没摘牌且还没亮出来 ⇒ 与 `_topUpGpuPrewarm` 用的同一颗尺(⛔ 不自立第二道闸)
|
|
305
|
+
prewarming: () => this._warmSeen !== null && !this.visible,
|
|
306
|
+
shown: () => this.visible,
|
|
307
|
+
pumpPano: (renderer) => this._stitcher.pump(renderer),
|
|
308
|
+
prefetchPano: () => this.prefetchHeadPanoramas(),
|
|
309
|
+
feedPlanes: () => this.feedImagePlaneTiles(),
|
|
310
|
+
});
|
|
311
|
+
/** 本对象的资源管理器(只读)。宿主只管调 `preRender`,需要看账本时才用它。 */
|
|
312
|
+
get resManager(): MktResourceManager {
|
|
313
|
+
return this._res;
|
|
314
|
+
}
|
|
315
|
+
|
|
289
316
|
/**
|
|
290
317
|
* 隐藏期 GPU 预热的在册清单(元素 = 一个「会进渲染列表的物件」,即它的材质组合 × 几何)。
|
|
291
318
|
* null = 没在预热(未启动 / 已跑完 / `stopGpuPrewarm` / `dispose` 四种都是它)。
|
|
@@ -334,7 +361,8 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
334
361
|
// `updateTexture`(它里面那记 `pump` 是「第一次拿到 renderer ⇒ 在册组整份作废」的现场)之后、
|
|
335
362
|
// `feedFaceMapping`(名册逐槽入册)之前 —— 顺序倒过来就是「名册在前、我们垫底」= 静默失效。
|
|
336
363
|
// 编辑态那份在 `MktDevMesh` 的宿主循环里照同一相对位置点(⛔ 别在这儿加第三处)。
|
|
337
|
-
// 2026-11-05 需求:这一批点几颗改由 `panoPriorityCount` 定(默认
|
|
364
|
+
// 2026-11-05 需求:这一批点几颗改由 `panoPriorityCount` 定(默认 -1 = 存档全部)⇒ 本句不动;
|
|
365
|
+
// 同一批在**隐藏期**由 `preRender(renderer)` 逐 tick 推(本钩子在那时一次都不触发)。
|
|
338
366
|
this.prefetchHeadPanoramas();
|
|
339
367
|
this.feedFaceMapping();
|
|
340
368
|
this.tickProgressiveReveal();
|
|
@@ -1372,12 +1400,15 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1372
1400
|
* 表空了它就是一句 `length` 判断,⛔ 不需要再像旧写法那样把钩子置 null(钩子上还挂着全景投影那三件事)。
|
|
1373
1401
|
* ⚠️ 这里写的 `visible` 只针对**自己压进表的那几颗** ⇒ 表外的对象(单颗新增、已 dispose、被 `releaseReveal`
|
|
1374
1402
|
* 摘牌的)一律不碰;要在放行前接管某颗,调 `releaseReveal(o)`。
|
|
1403
|
+
* ⭐ 2026-11-05 需求第 4 点「只已经预载入的资源才显示出来」:内容落定(`o.loaded`)之后还要问一句资源管理器
|
|
1404
|
+
* 「这一档画进显存了吗」—— 只有宿主调过 `preRender` 且预热还挂着牌时才拦,摘牌 / 没调过 ⇒ 一律放行(⛔ 不许拦出永久黑屏)。
|
|
1375
1405
|
*/
|
|
1376
1406
|
tickProgressiveReveal(): void {
|
|
1377
1407
|
const q = this._revealPending;
|
|
1378
1408
|
for (let i = 0; i < q.length; i++) {
|
|
1379
1409
|
const o = q[i];
|
|
1380
1410
|
if (!o.loaded) continue; // 内容还在路上 ⇒ 这一颗继续压着,先看后面已经落定的
|
|
1411
|
+
if (this._res.holdsReveal(o)) continue; // 内容到了但还没进显存 ⇒ 等 `preRender` 那一拍画过它
|
|
1381
1412
|
q.splice(i, 1);
|
|
1382
1413
|
o.visible = true;
|
|
1383
1414
|
return;
|
|
@@ -1386,6 +1417,26 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1386
1417
|
|
|
1387
1418
|
// ─────────────────── 隐藏期 GPU 预热(2026-11-01 需求:进厅那一下不要再现编着色器) ───────────────────
|
|
1388
1419
|
|
|
1420
|
+
/**
|
|
1421
|
+
* ⭐ 2026-11-05 需求第 1 点逐字:「需要提供一个 `preRender` 方法, 传入一个渲染器对象, 然后在这个方法里面使用
|
|
1422
|
+
* 渲染来预载入已经准备好的资源到 GPU」。隐藏期(`visible = false`)里 three 遍历第一句就把本对象连同子树跳过
|
|
1423
|
+
* ⇒ `onBeforeRender` 一次都不触发,全景瓦片不下载不拼合、平面不绑、GLB 与墙的 program 不编不传;
|
|
1424
|
+
* 本方法就是那句「藏起来的时候替它跑一遍渲染循环里的推进」,交进来的渲染器同时供拼合器与预热用。
|
|
1425
|
+
*
|
|
1426
|
+
* 一次点齐三族(次序固定,与 `onBeforeRender` 里同一相对位置):
|
|
1427
|
+
* ① 全景 —— `stitcher.pump(renderer)` 逐 tick 走「补下载 → 画已进显存的格 → 传一张新的」,
|
|
1428
|
+
* 额度由 `panoPriorityCount` 点的批排在前(默认 -1 = 整份存档,需求第 2 点);
|
|
1429
|
+
* ② 平面图 —— `feedImagePlaneTiles()` 一次把整层地址交给下载、绑定一帧一颗(需求第 3 点:这两族本来就全量发起);
|
|
1430
|
+
* ③ GLB / 平面 / 墙 —— 下面的 `startGpuPrewarm` 那一族逐 tick 画一件进 1×1 目标(program 编译 + 顶点缓冲首传)。
|
|
1431
|
+
*
|
|
1432
|
+
* 幂等 ⇒ 隐藏期里每帧点一次、或只在载入后点一次都行;已排进队列的那条链不会被重复起。
|
|
1433
|
+
* 本对象一旦 `visible`(进厅),链自己停 ⇒ 之后交回 `onBeforeRender` 每帧那一路,⛔ 不抢同一帧的额度。
|
|
1434
|
+
* ⛔ 不调本方法的宿主(发布页直接亮墙、编辑器画布)行为与改造前逐字一致:显示闸只在 `preRender` 点过之后才生效。
|
|
1435
|
+
*/
|
|
1436
|
+
preRender(renderer: WebGLRenderer): void {
|
|
1437
|
+
this._res.preRender(renderer);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1389
1440
|
/**
|
|
1390
1441
|
* 开始隐藏期预热:把「进厅第一帧要现编的那些 program + 要第一次上传的那些几何/纹理」提前跑掉,一帧只放**一件**。
|
|
1391
1442
|
*
|
|
@@ -1482,6 +1533,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1482
1533
|
if (seen.has(key)) return;
|
|
1483
1534
|
seen.add(key);
|
|
1484
1535
|
items.push({ src: o, m: mesh.material as Material | Material[], g });
|
|
1536
|
+
// ⭐ 2026-11-05 需求第 4 点:把「这一颗属于哪一档」交给资源管理器记账(键的算式仍只在这里这一处),
|
|
1537
|
+
// 等 `_runGpuWarmStep` 真画过它再翻成「已进显存」⇒ `tickProgressiveReveal` 才放行。
|
|
1538
|
+
this._res.noteWarmKey(o, key);
|
|
1485
1539
|
added++;
|
|
1486
1540
|
});
|
|
1487
1541
|
return added;
|
|
@@ -1531,6 +1585,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1531
1585
|
renderer.setRenderTarget(this._warmRt!);
|
|
1532
1586
|
renderer.render(scene, GPU_WARM_CAMERA);
|
|
1533
1587
|
renderer.setRenderTarget(prevTarget);
|
|
1588
|
+
// ⭐ 2026-11-05 需求第 4 点:这一档刚刚真画进显存(program 编了、顶点缓冲与纹理首传也做了)⇒ 记上「已常驻」,
|
|
1589
|
+
// `tickProgressiveReveal` 那一拍才许把对应那颗亮出来(同一档的第二、第三颗跟着一起放行)。
|
|
1590
|
+
this._res.markWarmDrawn(it.src);
|
|
1534
1591
|
this._scheduleGpuWarmStep(renderer);
|
|
1535
1592
|
}
|
|
1536
1593
|
|
|
@@ -1791,6 +1848,7 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1791
1848
|
this._revealPending = [];
|
|
1792
1849
|
// 隐藏期预热:清单、去重表与小屋子那套临时资源随本对象一起退场(那张 1×1 渲染目标是本类**唯一**自建的 RT)。
|
|
1793
1850
|
// 走 `stopGpuPrewarm` 而不是 `_finishGpuPrewarm`:本对象都要没了,去重表再留着就是泄漏。
|
|
1851
|
+
this._res.stop(); // 资源管理器:全景/平面那条链停、显示闸开、显存账作废(内部同样落到 `stopGpuPrewarm`)
|
|
1794
1852
|
this.stopGpuPrewarm();
|
|
1795
1853
|
this._stitcher.dispose();
|
|
1796
1854
|
this._glRenderer = null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MktMesh` 的资源管理器(2026-11-05 需求逐字:「只已经预载入的资源才显示出来, 或者放到 shader 里面去显示出来.
|
|
3
|
+
* 可以专门写一个资源管理器来管理, 每个 mktmesh 都有自己的一个资源管理器来管理」)。
|
|
4
|
+
*
|
|
5
|
+
* 管的是三族资源:全景(32 片拼成左右 2 张槽纹理)、GLB(模型槽)、平面图(4 格瓦片)。
|
|
6
|
+
* 职责只有两件事:
|
|
7
|
+
* ① **推**:`preRender(renderer)` 起一条全局队列链,每 tick 推进一步(全景 = 补下载 → 画已进显存的格 → 传一张新的;
|
|
8
|
+
* 平面 = 把瓦片交给下载并补排绑定;GLB/平面/墙 = 隐藏期预热那件 1×1 绘制)。
|
|
9
|
+
* 为什么非得有人推:`visible = false` 时 three 遍历第一句就 `if ( object.visible === false ) return;`
|
|
10
|
+
* ⇒ 墙一次都收不到 `onBeforeRender`,藏起来的展厅里那三族本来一帧都不推进(这就是「资源等不到渲染器」的根)。
|
|
11
|
+
* ② **记账**:哪一档真的画进过显存。`MktMesh.tickProgressiveReveal` 拿这本账决定一颗平面 / 一槽模型许不许亮
|
|
12
|
+
* ⇒ 「已经预载入的才显示出来」落在这一条上(需求第 4 点)。
|
|
13
|
+
*
|
|
14
|
+
* ⛔ 本文件不下载、不解码、不 `initTexture`、不建纹理、不拼合、不编译 —— 那几件事各自只有一份实现
|
|
15
|
+
* (`./panoTileStitcher` 的三道工序、`./imageTileLoader`、`./modelSlot`、`MktMesh` 的隐藏期预热)。
|
|
16
|
+
* 这里只经 `MktResHost` 那几只手**点它们**,并把预热报回来的那一档记上账 ⇒ 全工程只此一把「进显存」尺。
|
|
17
|
+
* ⚠️ 全景「进 shader」那一道闸不在这里:`MktMesh._peekTileAt` 见拼合器 `resident()` 为假就返回 null ⇒ 那一格当帧不绑,
|
|
18
|
+
* 已由「没进显存不许绑」管住;这里只管**驱动**与**亮出来的那一族**。
|
|
19
|
+
*/
|
|
20
|
+
import type { Object3D, WebGLRenderer } from "three";
|
|
21
|
+
/** 管理器向宿主(一颗 `MktMesh`)要的那几只手 —— 全部是宿主上现成的入口,⛔ 不许在这里另起一套实现。 */
|
|
22
|
+
export interface MktResHost {
|
|
23
|
+
/** 隐藏期 GPU 预热(一 tick 画一件:program 编译 + 顶点缓冲/纹理首传);重复点不重建清单 */
|
|
24
|
+
prewarm(renderer: WebGLRenderer): void;
|
|
25
|
+
/** 摘牌(进厅 / 退场):清单作废,晚到的内容不再补排 */
|
|
26
|
+
stopPrewarm(): void;
|
|
27
|
+
/** 预热还挂着牌吗(`_warmSeen` 非空且还没 `visible`)⇒ 只有挂着牌,「没进显存先别亮」这道闸才谈得上生效 */
|
|
28
|
+
prewarming(): boolean;
|
|
29
|
+
/** 已经亮出来了 ⇒ 每帧由 `onBeforeRender` 自己驱动,本管理器那条链就该停下来不抢额度 */
|
|
30
|
+
shown(): boolean;
|
|
31
|
+
/** 全景拼合推进一步(补下载 → 画已进显存的格 → 传一张新的);⚠️ 必须排在 `prefetchPano` **之前** */
|
|
32
|
+
pumpPano(renderer: WebGLRenderer): void;
|
|
33
|
+
/** 把存档前 n 颗全景(`panoPriorityCount`,n = -1 为整份)点名回加载任务队列头部;幂等 */
|
|
34
|
+
prefetchPano(): void;
|
|
35
|
+
/** 把整层平面的瓦片地址交给下载并补排「一帧一颗」的绑定;幂等 */
|
|
36
|
+
feedPlanes(): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 一颗 `MktMesh` 一份(`MktMesh._res`,由构造期注入的 `MktResHost` 绑回宿主)。
|
|
40
|
+
* 状态机很简单:`preRender` 把链起起来并开闸,链每 tick 推进一步,宿主亮出来(`shown()`)就自停;
|
|
41
|
+
* 摘牌(`stop()`)同时关掉显示闸 ⇒ 没用过 `preRender` 的宿主行为与改造前逐字一致。
|
|
42
|
+
*/
|
|
43
|
+
export declare class MktResourceManager {
|
|
44
|
+
private host;
|
|
45
|
+
/** 宿主交进来的那台渲染器(隐藏期库里自己拿不到 ⇒ 只有 `preRender` 这一路) */
|
|
46
|
+
private renderer;
|
|
47
|
+
/** 全景/平面那条链是否还排在 `threeUtils.workList` 里(防重复起链) */
|
|
48
|
+
private driving;
|
|
49
|
+
/** `preRender` 点过 ⇒ 显示闸才生效(⛔ 没点过的宿主一律照常放行,不许凭空黑屏) */
|
|
50
|
+
private armed;
|
|
51
|
+
/** 对象 → 预热清单里那一档的去重键。键的**算式**只在 `MktMesh._collectWarmItems` 一处,这里只登记结果 */
|
|
52
|
+
private warmKey;
|
|
53
|
+
/** 已经真画进显存的那几档(键)⇒ 同一档的第二第三颗也算过了,不必再压 */
|
|
54
|
+
private warmDrawn;
|
|
55
|
+
constructor(host: MktResHost);
|
|
56
|
+
/**
|
|
57
|
+
* 预载入:拿宿主的渲染器,把「已经准备好的」逐 tick 推进 GPU(需求第 1 点)。
|
|
58
|
+
* 幂等 ⇒ 隐藏期里宿主每帧点一次、或只点一次都行;换了渲染器再点 ⇒ 拼合器自己会把在册组整份作废(`_switchRenderer`)。
|
|
59
|
+
*/
|
|
60
|
+
preRender(renderer: WebGLRenderer | null): void;
|
|
61
|
+
/** 摘牌(宿主进厅 / `MktMesh.dispose`):链停、闸开、账清;预热交回宿主那句 `stopGpuPrewarm` 去收。 */
|
|
62
|
+
stop(): void;
|
|
63
|
+
/** 登记「这一颗对应预热清单里哪一档」(`_collectWarmItems` 收进来那一拍调)。 */
|
|
64
|
+
noteWarmKey(o: Object3D, key: string): void;
|
|
65
|
+
/** 记这一档刚刚真画进显存(`_runGpuWarmStep` 画完那一拍调)⇒ 这颗从此许亮。 */
|
|
66
|
+
markWarmDrawn(o: Object3D): void;
|
|
67
|
+
/**
|
|
68
|
+
* 显示闸(需求第 4 点):这颗平面 / 模型此刻该不该被压着不亮。
|
|
69
|
+
* 只在「`preRender` 点过 + 预热还挂着牌 + 这一档登记过但还没画过」三条同时成立时才拦;
|
|
70
|
+
* ⛔ 压根没进过预热清单的一律放行 —— 那属于「还没扫到」,把它当「没进显存」就是永久黑屏。
|
|
71
|
+
*/
|
|
72
|
+
holdsReveal(o: Object3D): boolean;
|
|
73
|
+
/** 队列里那一格:推进一步,然后只再排自己下一格(⛔ 不在这里一轮推光)。 */
|
|
74
|
+
private step;
|
|
75
|
+
/** 一步里干的三件事,次序固定:先推拼合(第一次拿到渲染器时它把在册组整份作废),再点名,再补排平面绑定。 */
|
|
76
|
+
private feed;
|
|
77
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import threeUtils from "../threeUtils";
|
|
2
|
+
/**
|
|
3
|
+
* 一颗 `MktMesh` 一份(`MktMesh._res`,由构造期注入的 `MktResHost` 绑回宿主)。
|
|
4
|
+
* 状态机很简单:`preRender` 把链起起来并开闸,链每 tick 推进一步,宿主亮出来(`shown()`)就自停;
|
|
5
|
+
* 摘牌(`stop()`)同时关掉显示闸 ⇒ 没用过 `preRender` 的宿主行为与改造前逐字一致。
|
|
6
|
+
*/
|
|
7
|
+
export class MktResourceManager {
|
|
8
|
+
host;
|
|
9
|
+
/** 宿主交进来的那台渲染器(隐藏期库里自己拿不到 ⇒ 只有 `preRender` 这一路) */
|
|
10
|
+
renderer = null;
|
|
11
|
+
/** 全景/平面那条链是否还排在 `threeUtils.workList` 里(防重复起链) */
|
|
12
|
+
driving = false;
|
|
13
|
+
/** `preRender` 点过 ⇒ 显示闸才生效(⛔ 没点过的宿主一律照常放行,不许凭空黑屏) */
|
|
14
|
+
armed = false;
|
|
15
|
+
/** 对象 → 预热清单里那一档的去重键。键的**算式**只在 `MktMesh._collectWarmItems` 一处,这里只登记结果 */
|
|
16
|
+
warmKey = new WeakMap();
|
|
17
|
+
/** 已经真画进显存的那几档(键)⇒ 同一档的第二第三颗也算过了,不必再压 */
|
|
18
|
+
warmDrawn = new Set();
|
|
19
|
+
constructor(host) {
|
|
20
|
+
this.host = host;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 预载入:拿宿主的渲染器,把「已经准备好的」逐 tick 推进 GPU(需求第 1 点)。
|
|
24
|
+
* 幂等 ⇒ 隐藏期里宿主每帧点一次、或只点一次都行;换了渲染器再点 ⇒ 拼合器自己会把在册组整份作废(`_switchRenderer`)。
|
|
25
|
+
*/
|
|
26
|
+
preRender(renderer) {
|
|
27
|
+
if (!renderer)
|
|
28
|
+
return;
|
|
29
|
+
this.armed = true;
|
|
30
|
+
this.renderer = renderer;
|
|
31
|
+
this.host.prewarm(renderer); // GLB / 平面图 / 墙:一 tick 一件,它自己会接着排下一件
|
|
32
|
+
this.feed(); // 全景 + 平面:先立刻推一步,别让第一格空转
|
|
33
|
+
if (this.driving)
|
|
34
|
+
return;
|
|
35
|
+
this.driving = true;
|
|
36
|
+
threeUtils.addWork(() => { }); // 占一格 ⇒ 真活落下一 tick,与预热 / 上传 / 绑定同一手法
|
|
37
|
+
threeUtils.addWork(() => this.step());
|
|
38
|
+
}
|
|
39
|
+
/** 摘牌(宿主进厅 / `MktMesh.dispose`):链停、闸开、账清;预热交回宿主那句 `stopGpuPrewarm` 去收。 */
|
|
40
|
+
stop() {
|
|
41
|
+
this.driving = false;
|
|
42
|
+
this.armed = false;
|
|
43
|
+
this.renderer = null;
|
|
44
|
+
this.warmDrawn.clear();
|
|
45
|
+
this.host.stopPrewarm();
|
|
46
|
+
}
|
|
47
|
+
/** 登记「这一颗对应预热清单里哪一档」(`_collectWarmItems` 收进来那一拍调)。 */
|
|
48
|
+
noteWarmKey(o, key) {
|
|
49
|
+
this.warmKey.set(o, key);
|
|
50
|
+
}
|
|
51
|
+
/** 记这一档刚刚真画进显存(`_runGpuWarmStep` 画完那一拍调)⇒ 这颗从此许亮。 */
|
|
52
|
+
markWarmDrawn(o) {
|
|
53
|
+
const key = this.warmKey.get(o);
|
|
54
|
+
if (key)
|
|
55
|
+
this.warmDrawn.add(key);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 显示闸(需求第 4 点):这颗平面 / 模型此刻该不该被压着不亮。
|
|
59
|
+
* 只在「`preRender` 点过 + 预热还挂着牌 + 这一档登记过但还没画过」三条同时成立时才拦;
|
|
60
|
+
* ⛔ 压根没进过预热清单的一律放行 —— 那属于「还没扫到」,把它当「没进显存」就是永久黑屏。
|
|
61
|
+
*/
|
|
62
|
+
holdsReveal(o) {
|
|
63
|
+
if (!this.armed || !this.host.prewarming())
|
|
64
|
+
return false;
|
|
65
|
+
const key = this.warmKey.get(o);
|
|
66
|
+
return key !== undefined && !this.warmDrawn.has(key);
|
|
67
|
+
}
|
|
68
|
+
/** 队列里那一格:推进一步,然后只再排自己下一格(⛔ 不在这里一轮推光)。 */
|
|
69
|
+
step() {
|
|
70
|
+
if (!this.driving)
|
|
71
|
+
return; // `stop()` 已摘牌 ⇒ 这一项作废
|
|
72
|
+
if (this.host.shown()) { // 亮出来了 ⇒ 交回 `onBeforeRender` 每帧那一路,不再抢额度
|
|
73
|
+
this.driving = false;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.feed();
|
|
77
|
+
threeUtils.addWork(() => { });
|
|
78
|
+
threeUtils.addWork(() => this.step());
|
|
79
|
+
}
|
|
80
|
+
/** 一步里干的三件事,次序固定:先推拼合(第一次拿到渲染器时它把在册组整份作废),再点名,再补排平面绑定。 */
|
|
81
|
+
feed() {
|
|
82
|
+
const renderer = this.renderer;
|
|
83
|
+
if (!renderer)
|
|
84
|
+
return;
|
|
85
|
+
this.host.pumpPano(renderer);
|
|
86
|
+
this.host.prefetchPano();
|
|
87
|
+
this.host.feedPlanes();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MktMesh` 的资源管理器(2026-11-05 需求逐字:「只已经预载入的资源才显示出来, 或者放到 shader 里面去显示出来.
|
|
3
|
+
* 可以专门写一个资源管理器来管理, 每个 mktmesh 都有自己的一个资源管理器来管理」)。
|
|
4
|
+
*
|
|
5
|
+
* 管的是三族资源:全景(32 片拼成左右 2 张槽纹理)、GLB(模型槽)、平面图(4 格瓦片)。
|
|
6
|
+
* 职责只有两件事:
|
|
7
|
+
* ① **推**:`preRender(renderer)` 起一条全局队列链,每 tick 推进一步(全景 = 补下载 → 画已进显存的格 → 传一张新的;
|
|
8
|
+
* 平面 = 把瓦片交给下载并补排绑定;GLB/平面/墙 = 隐藏期预热那件 1×1 绘制)。
|
|
9
|
+
* 为什么非得有人推:`visible = false` 时 three 遍历第一句就 `if ( object.visible === false ) return;`
|
|
10
|
+
* ⇒ 墙一次都收不到 `onBeforeRender`,藏起来的展厅里那三族本来一帧都不推进(这就是「资源等不到渲染器」的根)。
|
|
11
|
+
* ② **记账**:哪一档真的画进过显存。`MktMesh.tickProgressiveReveal` 拿这本账决定一颗平面 / 一槽模型许不许亮
|
|
12
|
+
* ⇒ 「已经预载入的才显示出来」落在这一条上(需求第 4 点)。
|
|
13
|
+
*
|
|
14
|
+
* ⛔ 本文件不下载、不解码、不 `initTexture`、不建纹理、不拼合、不编译 —— 那几件事各自只有一份实现
|
|
15
|
+
* (`./panoTileStitcher` 的三道工序、`./imageTileLoader`、`./modelSlot`、`MktMesh` 的隐藏期预热)。
|
|
16
|
+
* 这里只经 `MktResHost` 那几只手**点它们**,并把预热报回来的那一档记上账 ⇒ 全工程只此一把「进显存」尺。
|
|
17
|
+
* ⚠️ 全景「进 shader」那一道闸不在这里:`MktMesh._peekTileAt` 见拼合器 `resident()` 为假就返回 null ⇒ 那一格当帧不绑,
|
|
18
|
+
* 已由「没进显存不许绑」管住;这里只管**驱动**与**亮出来的那一族**。
|
|
19
|
+
*/
|
|
20
|
+
import type { Object3D, WebGLRenderer } from "three";
|
|
21
|
+
import threeUtils from "../threeUtils";
|
|
22
|
+
|
|
23
|
+
/** 管理器向宿主(一颗 `MktMesh`)要的那几只手 —— 全部是宿主上现成的入口,⛔ 不许在这里另起一套实现。 */
|
|
24
|
+
export interface MktResHost {
|
|
25
|
+
/** 隐藏期 GPU 预热(一 tick 画一件:program 编译 + 顶点缓冲/纹理首传);重复点不重建清单 */
|
|
26
|
+
prewarm(renderer: WebGLRenderer): void;
|
|
27
|
+
/** 摘牌(进厅 / 退场):清单作废,晚到的内容不再补排 */
|
|
28
|
+
stopPrewarm(): void;
|
|
29
|
+
/** 预热还挂着牌吗(`_warmSeen` 非空且还没 `visible`)⇒ 只有挂着牌,「没进显存先别亮」这道闸才谈得上生效 */
|
|
30
|
+
prewarming(): boolean;
|
|
31
|
+
/** 已经亮出来了 ⇒ 每帧由 `onBeforeRender` 自己驱动,本管理器那条链就该停下来不抢额度 */
|
|
32
|
+
shown(): boolean;
|
|
33
|
+
/** 全景拼合推进一步(补下载 → 画已进显存的格 → 传一张新的);⚠️ 必须排在 `prefetchPano` **之前** */
|
|
34
|
+
pumpPano(renderer: WebGLRenderer): void;
|
|
35
|
+
/** 把存档前 n 颗全景(`panoPriorityCount`,n = -1 为整份)点名回加载任务队列头部;幂等 */
|
|
36
|
+
prefetchPano(): void;
|
|
37
|
+
/** 把整层平面的瓦片地址交给下载并补排「一帧一颗」的绑定;幂等 */
|
|
38
|
+
feedPlanes(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 一颗 `MktMesh` 一份(`MktMesh._res`,由构造期注入的 `MktResHost` 绑回宿主)。
|
|
43
|
+
* 状态机很简单:`preRender` 把链起起来并开闸,链每 tick 推进一步,宿主亮出来(`shown()`)就自停;
|
|
44
|
+
* 摘牌(`stop()`)同时关掉显示闸 ⇒ 没用过 `preRender` 的宿主行为与改造前逐字一致。
|
|
45
|
+
*/
|
|
46
|
+
export class MktResourceManager {
|
|
47
|
+
private host: MktResHost;
|
|
48
|
+
/** 宿主交进来的那台渲染器(隐藏期库里自己拿不到 ⇒ 只有 `preRender` 这一路) */
|
|
49
|
+
private renderer: WebGLRenderer | null = null;
|
|
50
|
+
/** 全景/平面那条链是否还排在 `threeUtils.workList` 里(防重复起链) */
|
|
51
|
+
private driving = false;
|
|
52
|
+
/** `preRender` 点过 ⇒ 显示闸才生效(⛔ 没点过的宿主一律照常放行,不许凭空黑屏) */
|
|
53
|
+
private armed = false;
|
|
54
|
+
/** 对象 → 预热清单里那一档的去重键。键的**算式**只在 `MktMesh._collectWarmItems` 一处,这里只登记结果 */
|
|
55
|
+
private warmKey = new WeakMap<Object3D, string>();
|
|
56
|
+
/** 已经真画进显存的那几档(键)⇒ 同一档的第二第三颗也算过了,不必再压 */
|
|
57
|
+
private warmDrawn = new Set<string>();
|
|
58
|
+
|
|
59
|
+
constructor(host: MktResHost) {
|
|
60
|
+
this.host = host;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 预载入:拿宿主的渲染器,把「已经准备好的」逐 tick 推进 GPU(需求第 1 点)。
|
|
65
|
+
* 幂等 ⇒ 隐藏期里宿主每帧点一次、或只点一次都行;换了渲染器再点 ⇒ 拼合器自己会把在册组整份作废(`_switchRenderer`)。
|
|
66
|
+
*/
|
|
67
|
+
preRender(renderer: WebGLRenderer | null): void {
|
|
68
|
+
if (!renderer) return;
|
|
69
|
+
this.armed = true;
|
|
70
|
+
this.renderer = renderer;
|
|
71
|
+
this.host.prewarm(renderer); // GLB / 平面图 / 墙:一 tick 一件,它自己会接着排下一件
|
|
72
|
+
this.feed(); // 全景 + 平面:先立刻推一步,别让第一格空转
|
|
73
|
+
if (this.driving) return;
|
|
74
|
+
this.driving = true;
|
|
75
|
+
threeUtils.addWork(() => {}); // 占一格 ⇒ 真活落下一 tick,与预热 / 上传 / 绑定同一手法
|
|
76
|
+
threeUtils.addWork(() => this.step());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 摘牌(宿主进厅 / `MktMesh.dispose`):链停、闸开、账清;预热交回宿主那句 `stopGpuPrewarm` 去收。 */
|
|
80
|
+
stop(): void {
|
|
81
|
+
this.driving = false;
|
|
82
|
+
this.armed = false;
|
|
83
|
+
this.renderer = null;
|
|
84
|
+
this.warmDrawn.clear();
|
|
85
|
+
this.host.stopPrewarm();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 登记「这一颗对应预热清单里哪一档」(`_collectWarmItems` 收进来那一拍调)。 */
|
|
89
|
+
noteWarmKey(o: Object3D, key: string): void {
|
|
90
|
+
this.warmKey.set(o, key);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 记这一档刚刚真画进显存(`_runGpuWarmStep` 画完那一拍调)⇒ 这颗从此许亮。 */
|
|
94
|
+
markWarmDrawn(o: Object3D): void {
|
|
95
|
+
const key = this.warmKey.get(o);
|
|
96
|
+
if (key) this.warmDrawn.add(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 显示闸(需求第 4 点):这颗平面 / 模型此刻该不该被压着不亮。
|
|
101
|
+
* 只在「`preRender` 点过 + 预热还挂着牌 + 这一档登记过但还没画过」三条同时成立时才拦;
|
|
102
|
+
* ⛔ 压根没进过预热清单的一律放行 —— 那属于「还没扫到」,把它当「没进显存」就是永久黑屏。
|
|
103
|
+
*/
|
|
104
|
+
holdsReveal(o: Object3D): boolean {
|
|
105
|
+
if (!this.armed || !this.host.prewarming()) return false;
|
|
106
|
+
const key = this.warmKey.get(o);
|
|
107
|
+
return key !== undefined && !this.warmDrawn.has(key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 队列里那一格:推进一步,然后只再排自己下一格(⛔ 不在这里一轮推光)。 */
|
|
111
|
+
private step(): void {
|
|
112
|
+
if (!this.driving) return; // `stop()` 已摘牌 ⇒ 这一项作废
|
|
113
|
+
if (this.host.shown()) { // 亮出来了 ⇒ 交回 `onBeforeRender` 每帧那一路,不再抢额度
|
|
114
|
+
this.driving = false;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.feed();
|
|
118
|
+
threeUtils.addWork(() => {});
|
|
119
|
+
threeUtils.addWork(() => this.step());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 一步里干的三件事,次序固定:先推拼合(第一次拿到渲染器时它把在册组整份作废),再点名,再补排平面绑定。 */
|
|
123
|
+
private feed(): void {
|
|
124
|
+
const renderer = this.renderer;
|
|
125
|
+
if (!renderer) return;
|
|
126
|
+
this.host.pumpPano(renderer);
|
|
127
|
+
this.host.prefetchPano();
|
|
128
|
+
this.host.feedPlanes();
|
|
129
|
+
}
|
|
130
|
+
}
|