three-base 2.0.181 → 2.0.187
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 +36 -6
- package/MktMesh/MktMesh.js +107 -27
- package/MktMesh/MktMesh.ts +103 -26
- package/MktMesh/imagePlane.d.ts +4 -1
- package/MktMesh/imagePlane.js +25 -10
- package/MktMesh/imagePlane.ts +26 -10
- package/MktMesh/imageTileLoader.d.ts +2 -0
- package/MktMesh/imageTileLoader.js +5 -1
- package/MktMesh/imageTileLoader.ts +6 -1
- package/MktMesh/mktMappingShader.d.ts +5 -5
- package/MktMesh/mktMappingShader.js +12 -12
- package/MktMesh/mktMappingShader.ts +12 -12
- package/MktMesh/mktShader.d.ts +57 -56
- package/MktMesh/mktShader.js +62 -60
- package/MktMesh/mktShader.ts +72 -70
- package/MktMesh/modelSlot.d.ts +21 -3
- package/MktMesh/modelSlot.js +39 -4
- package/MktMesh/modelSlot.ts +38 -4
- package/MktMesh/panoSlotRoster.d.ts +2 -2
- package/MktMesh/panoSlotRoster.js +4 -4
- package/MktMesh/panoSlotRoster.ts +4 -4
- package/MktMesh/panoTileStitcher.d.ts +7 -7
- package/MktMesh/panoTileStitcher.js +33 -28
- package/MktMesh/panoTileStitcher.ts +34 -29
- package/package.json +1 -1
package/MktMesh/MktMesh.d.ts
CHANGED
|
@@ -130,9 +130,23 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
130
130
|
models: ModelSlot[];
|
|
131
131
|
/**
|
|
132
132
|
* 那 4 张瓦片的下载/解码缓存。⭐ 与全景 `_texCache` 同一条旨(10-02「纹理都缓存在对象里面,执行 dispose 时才销毁」)
|
|
133
|
-
* ⇒
|
|
133
|
+
* ⇒ 切工程**不**清它,回来那一圈直接命中缓存、不会再空一格;只随本对象 `dispose()` 释放。
|
|
134
134
|
*/
|
|
135
135
|
private _imgTileLoader;
|
|
136
|
+
/**
|
|
137
|
+
* 逐帧绑定用的两张账(2026-10-26 定的方案:地址一次性全部丢给下载,绑材质改成「一帧一颗平面」)。
|
|
138
|
+
* 全局 rAF 每拍只跑一项 `addWork`,而 `addWork` 按闭包引用判重 ⇒ 每次现造的新闭包它判不出来,
|
|
139
|
+
* ⛔ 去重闸门必须像全景上传那张 `_uploadQueued` 一样自备。
|
|
140
|
+
*/
|
|
141
|
+
private _imgBindQueued;
|
|
142
|
+
/** 平面 → 「它 4 条地址全部落定(就绪或失败)时的那串地址」⇒ 地址不换就不再重排;上传换图会改这串 ⇒ 自动重排 */
|
|
143
|
+
private _imgBindDone;
|
|
144
|
+
/**
|
|
145
|
+
* 模型槽「GLB 已取回、等这一帧把它挂进父元素」的在册表 —— 与上面图片绑定同一手法(用户 10-26 需求:
|
|
146
|
+
* 一次性全部拿去加载,用 `threeUtils.addWork` 逐帧加到场景里)。整层十几个槽同时到货时,
|
|
147
|
+
* 一帧只有一颗进 `_contentRoot`(进图那一拍要重算包围盒 + 排布 + 首次把几何送上 GPU)。
|
|
148
|
+
*/
|
|
149
|
+
private _modelMountQueued;
|
|
136
150
|
constructor(opts?: {
|
|
137
151
|
wallHeight?: number;
|
|
138
152
|
wallClosed?: boolean;
|
|
@@ -309,13 +323,13 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
309
323
|
projectName: string;
|
|
310
324
|
/** 注入项目名(导出 json 前调一次即可;导入时无须调——还原靠落盘地址自身的首段) */
|
|
311
325
|
setProjectName(name: string): void;
|
|
312
|
-
/** 单颗图片平面入层(编辑态双击落点用):进数组 + 挂进本对象子树 */
|
|
326
|
+
/** 单颗图片平面入层(编辑态双击落点用):进数组 + 挂进本对象子树 + 起排它自己的逐帧绑定 */
|
|
313
327
|
addImagePlane(ip: ImagePlane): ImagePlane;
|
|
314
328
|
/** 摘掉一颗并释放它自己的几何/材质;那 4 张瓦片纹理归 `_imgTileLoader` 管 ⇒ ⛔ 不在这里 dispose */
|
|
315
329
|
removeImagePlane(ip: ImagePlane): void;
|
|
316
330
|
/** 单颗模型槽入层(编辑态双击落点、以及「复制模型」用) */
|
|
317
331
|
addModel(ms: ModelSlot): ModelSlot;
|
|
318
|
-
/** 摘掉一槽并释放(子树里的几何/材质/贴图由 `ModelSlot.dispose`
|
|
332
|
+
/** 摘掉一槽并释放(子树里的几何/材质/贴图由 `ModelSlot.dispose` 自己收尾;排在后头的挂载帧靠「不在册」早退) */
|
|
319
333
|
removeModel(ms: ModelSlot): void;
|
|
320
334
|
/**
|
|
321
335
|
* 整层重建图片平面(`importData` 与预览那一路)。
|
|
@@ -327,14 +341,30 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
327
341
|
* 整层重建模型槽(`importData` 与预览那一路),地址口径的要求同 `setImagePlanes`。
|
|
328
342
|
* ⚠️ GLB 逐只**异步取、不 await**(十几只串一条队会把载入卡死),每只落地那一刻各回调一次 `onEachLoad`:
|
|
329
343
|
* 预览每帧真画 ⇒ 不传;编辑器是按需渲染 ⇒ 传一个「标脏」的钩子,否则模型挂上来屏幕不动。
|
|
344
|
+
* 2026-10-26 决定:取回与「挂进父元素」分两拍 —— 地址一次性全交给下载(网络 + 解析不占渲染帧),
|
|
345
|
+
* 真正进场景图那一步交给 `threeUtils.addWork` ⇒ 全局 rAF 每拍只跑一项 = **一帧只挂一颗槽**
|
|
346
|
+
* (挂载那一拍要重算包围盒 + 排布 + 首次把整棵子树的几何送上 GPU,十几颗挤在同一帧就是他要拆掉的那一卡)。
|
|
330
347
|
*/
|
|
331
348
|
setModels(datas: ModelSlotData[], onEachLoad?: () => void): void;
|
|
349
|
+
/** 把「备好待挂」的那一槽交给全局队列;⛔ 去重闸门自备(`addWork` 按闭包引用判重,新闭包它判不出来) */
|
|
350
|
+
private _queueModelMount;
|
|
351
|
+
/** 全局队列里的那一帧:只挂这一槽,挂好才回调(`onEachLoad` = 按需渲染宿主的标脏钩子) */
|
|
352
|
+
private _mountModelStep;
|
|
332
353
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
354
|
+
* 把各颗平面那 4 张瓦片的纹理绑进材质(10-22 晚定的口径:不走拼合器 RT)。
|
|
355
|
+
* 2026-10-26 决定改成**逐帧**:一次把整层地址全丢给下载器(下载/解码在网络与工作线程侧,不占这一帧),
|
|
356
|
+
* 真正写 uniform 的那一步交给 `threeUtils.addWork` —— 全局 rAF 每拍只跑一项 ⇒ 一帧只绑一颗平面,
|
|
357
|
+
* 一整层平面的纹理上传被摊到多帧,不再挤在同一记调用里(旧写法一记 `feed` 就把所有平面全绑完)。
|
|
358
|
+
* ⛔ 不进 `onBeforeRender` 自驱;持有渲染循环的那一方(编辑态 / 预览 / 下游)每帧调一次只是「补排漏网的平面」,
|
|
359
|
+
* 而整层重建(`setImagePlanes`)末尾自己起排 ⇒ 只调 `importData(整串)` 的宿主不必自己驱动也会长出来。
|
|
360
|
+
* 还没下载完/缺格的那一格绑 null ⇒ 片元那一格整格 discard,不落像素(用户 10-26 连收两条:先「不要默认图」,
|
|
361
|
+
* 再「不是显示灰色, 而是直接 discard 丢弃」)。
|
|
336
362
|
*/
|
|
337
363
|
feedImagePlaneTiles(): void;
|
|
364
|
+
/** 一颗平面排一帧的绑定工作:先把它那 4 条地址**一次性全部**交给下载器,再把「写材质」排进全局队列。 */
|
|
365
|
+
private _queueImagePlaneBind;
|
|
366
|
+
/** 队列里的一格:只绑**这一颗**平面里已就绪的那几格;还有在途的就重新排队,⛔ 不在这里等、也⛔ 不顺手把别的平面一起做完。 */
|
|
367
|
+
private _bindImagePlaneStep;
|
|
338
368
|
/** 导出数据:墙点位 + 墙高 + 是否闭合 + 纹理数组(url + 矩阵)
|
|
339
369
|
* @param projName 项目名(可选,优先于 `setProjectName` 注入的那个)——图片地址落成 `{项目名}/xxx.jpg` */
|
|
340
370
|
exportData(projName?: string): MktMeshData;
|
package/MktMesh/MktMesh.js
CHANGED
|
@@ -187,9 +187,23 @@ export default class MktMesh extends Mesh {
|
|
|
187
187
|
models = [];
|
|
188
188
|
/**
|
|
189
189
|
* 那 4 张瓦片的下载/解码缓存。⭐ 与全景 `_texCache` 同一条旨(10-02「纹理都缓存在对象里面,执行 dispose 时才销毁」)
|
|
190
|
-
* ⇒
|
|
190
|
+
* ⇒ 切工程**不**清它,回来那一圈直接命中缓存、不会再空一格;只随本对象 `dispose()` 释放。
|
|
191
191
|
*/
|
|
192
192
|
_imgTileLoader = new ImagePlaneTileLoader();
|
|
193
|
+
/**
|
|
194
|
+
* 逐帧绑定用的两张账(2026-10-26 定的方案:地址一次性全部丢给下载,绑材质改成「一帧一颗平面」)。
|
|
195
|
+
* 全局 rAF 每拍只跑一项 `addWork`,而 `addWork` 按闭包引用判重 ⇒ 每次现造的新闭包它判不出来,
|
|
196
|
+
* ⛔ 去重闸门必须像全景上传那张 `_uploadQueued` 一样自备。
|
|
197
|
+
*/
|
|
198
|
+
_imgBindQueued = new Set();
|
|
199
|
+
/** 平面 → 「它 4 条地址全部落定(就绪或失败)时的那串地址」⇒ 地址不换就不再重排;上传换图会改这串 ⇒ 自动重排 */
|
|
200
|
+
_imgBindDone = new Map();
|
|
201
|
+
/**
|
|
202
|
+
* 模型槽「GLB 已取回、等这一帧把它挂进父元素」的在册表 —— 与上面图片绑定同一手法(用户 10-26 需求:
|
|
203
|
+
* 一次性全部拿去加载,用 `threeUtils.addWork` 逐帧加到场景里)。整层十几个槽同时到货时,
|
|
204
|
+
* 一帧只有一颗进 `_contentRoot`(进图那一拍要重算包围盒 + 排布 + 首次把几何送上 GPU)。
|
|
205
|
+
*/
|
|
206
|
+
_modelMountQueued = new Set();
|
|
193
207
|
constructor(opts = {}) {
|
|
194
208
|
// 发布/预览态墙:**固定**用「全景序号直取」着色器(皇上旨 2026-09-29:mktmesh 就只有按照 attribute
|
|
195
209
|
// 配置来显示全景这一种固定的 shader——每帧由 feedFaceMapping 按 panoFaceMapping 把被引用的全景
|
|
@@ -1027,10 +1041,11 @@ export default class MktMesh extends Mesh {
|
|
|
1027
1041
|
this.projectName = typeof name === "string" ? name : "";
|
|
1028
1042
|
}
|
|
1029
1043
|
// ───────────── 平面图 / 模型两层(⭐ 皇上旨 2026-10-26「把图片平面和模型一起搬进库里」) ─────────────
|
|
1030
|
-
/** 单颗图片平面入层(编辑态双击落点用):进数组 + 挂进本对象子树 */
|
|
1044
|
+
/** 单颗图片平面入层(编辑态双击落点用):进数组 + 挂进本对象子树 + 起排它自己的逐帧绑定 */
|
|
1031
1045
|
addImagePlane(ip) {
|
|
1032
1046
|
this.imagePlanes.push(ip);
|
|
1033
1047
|
this.add(ip);
|
|
1048
|
+
this._queueImagePlaneBind(ip);
|
|
1034
1049
|
return ip;
|
|
1035
1050
|
}
|
|
1036
1051
|
/** 摘掉一颗并释放它自己的几何/材质;那 4 张瓦片纹理归 `_imgTileLoader` 管 ⇒ ⛔ 不在这里 dispose */
|
|
@@ -1038,6 +1053,8 @@ export default class MktMesh extends Mesh {
|
|
|
1038
1053
|
const i = this.imagePlanes.indexOf(ip);
|
|
1039
1054
|
if (i >= 0)
|
|
1040
1055
|
this.imagePlanes.splice(i, 1);
|
|
1056
|
+
this._imgBindQueued.delete(ip);
|
|
1057
|
+
this._imgBindDone.delete(ip);
|
|
1041
1058
|
this.remove(ip);
|
|
1042
1059
|
ip.dispose();
|
|
1043
1060
|
}
|
|
@@ -1047,11 +1064,12 @@ export default class MktMesh extends Mesh {
|
|
|
1047
1064
|
this.add(ms);
|
|
1048
1065
|
return ms;
|
|
1049
1066
|
}
|
|
1050
|
-
/** 摘掉一槽并释放(子树里的几何/材质/贴图由 `ModelSlot.dispose`
|
|
1067
|
+
/** 摘掉一槽并释放(子树里的几何/材质/贴图由 `ModelSlot.dispose` 自己收尾;排在后头的挂载帧靠「不在册」早退) */
|
|
1051
1068
|
removeModel(ms) {
|
|
1052
1069
|
const i = this.models.indexOf(ms);
|
|
1053
1070
|
if (i >= 0)
|
|
1054
1071
|
this.models.splice(i, 1);
|
|
1072
|
+
this._modelMountQueued.delete(ms);
|
|
1055
1073
|
this.remove(ms);
|
|
1056
1074
|
ms.dispose();
|
|
1057
1075
|
}
|
|
@@ -1061,6 +1079,8 @@ export default class MktMesh extends Mesh {
|
|
|
1061
1079
|
* ⛔ 本方法里不许再补一遍,也⛔ 不许在调用方各自补。
|
|
1062
1080
|
*/
|
|
1063
1081
|
setImagePlanes(datas) {
|
|
1082
|
+
this._imgBindQueued.clear();
|
|
1083
|
+
this._imgBindDone.clear();
|
|
1064
1084
|
for (const ip of this.imagePlanes) {
|
|
1065
1085
|
this.remove(ip);
|
|
1066
1086
|
ip.dispose();
|
|
@@ -1072,13 +1092,19 @@ export default class MktMesh extends Mesh {
|
|
|
1072
1092
|
this.imagePlanes.push(ip);
|
|
1073
1093
|
this.add(ip);
|
|
1074
1094
|
}
|
|
1095
|
+
// 整层重建完立刻起排:地址一次性全交给下载、绑材质逐帧一记 ⇒ 宿主不驱动这一层也长得出来
|
|
1096
|
+
this.feedImagePlaneTiles();
|
|
1075
1097
|
}
|
|
1076
1098
|
/**
|
|
1077
1099
|
* 整层重建模型槽(`importData` 与预览那一路),地址口径的要求同 `setImagePlanes`。
|
|
1078
1100
|
* ⚠️ GLB 逐只**异步取、不 await**(十几只串一条队会把载入卡死),每只落地那一刻各回调一次 `onEachLoad`:
|
|
1079
1101
|
* 预览每帧真画 ⇒ 不传;编辑器是按需渲染 ⇒ 传一个「标脏」的钩子,否则模型挂上来屏幕不动。
|
|
1102
|
+
* 2026-10-26 决定:取回与「挂进父元素」分两拍 —— 地址一次性全交给下载(网络 + 解析不占渲染帧),
|
|
1103
|
+
* 真正进场景图那一步交给 `threeUtils.addWork` ⇒ 全局 rAF 每拍只跑一项 = **一帧只挂一颗槽**
|
|
1104
|
+
* (挂载那一拍要重算包围盒 + 排布 + 首次把整棵子树的几何送上 GPU,十几颗挤在同一帧就是他要拆掉的那一卡)。
|
|
1080
1105
|
*/
|
|
1081
1106
|
setModels(datas, onEachLoad) {
|
|
1107
|
+
this._modelMountQueued.clear();
|
|
1082
1108
|
for (const ms of this.models) {
|
|
1083
1109
|
this.remove(ms);
|
|
1084
1110
|
ms.dispose();
|
|
@@ -1088,42 +1114,93 @@ export default class MktMesh extends Mesh {
|
|
|
1088
1114
|
const ms = new ModelSlot(d.name);
|
|
1089
1115
|
ms.applyData(d);
|
|
1090
1116
|
this.models.push(ms);
|
|
1091
|
-
this.add(ms);
|
|
1117
|
+
this.add(ms); // 槽本体(占位小立方体)当场进图 ⇒ GLB 在路上时这一槽不凭空消失
|
|
1092
1118
|
if (d.url)
|
|
1093
|
-
void ms.
|
|
1119
|
+
void ms.loadModel(d.url).then(() => this._queueModelMount(ms, onEachLoad));
|
|
1094
1120
|
}
|
|
1095
1121
|
}
|
|
1122
|
+
/** 把「备好待挂」的那一槽交给全局队列;⛔ 去重闸门自备(`addWork` 按闭包引用判重,新闭包它判不出来) */
|
|
1123
|
+
_queueModelMount(ms, onEachLoad) {
|
|
1124
|
+
if (this._modelMountQueued.has(ms))
|
|
1125
|
+
return;
|
|
1126
|
+
this._modelMountQueued.add(ms);
|
|
1127
|
+
threeUtils.addWork(() => { });
|
|
1128
|
+
threeUtils.addWork(() => this._mountModelStep(ms, onEachLoad));
|
|
1129
|
+
}
|
|
1130
|
+
/** 全局队列里的那一帧:只挂这一槽,挂好才回调(`onEachLoad` = 按需渲染宿主的标脏钩子) */
|
|
1131
|
+
_mountModelStep(ms, onEachLoad) {
|
|
1132
|
+
this._modelMountQueued.delete(ms);
|
|
1133
|
+
// 槽已退场(换项目 / 删除):待挂那棵由 `ModelSlot.dispose` → `_detachModel` 当场释放,这里不挂
|
|
1134
|
+
if (!this.models.includes(ms))
|
|
1135
|
+
return;
|
|
1136
|
+
if (ms.mountStaged() && onEachLoad)
|
|
1137
|
+
onEachLoad();
|
|
1138
|
+
}
|
|
1096
1139
|
/**
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1140
|
+
* 把各颗平面那 4 张瓦片的纹理绑进材质(10-22 晚定的口径:不走拼合器 RT)。
|
|
1141
|
+
* 2026-10-26 决定改成**逐帧**:一次把整层地址全丢给下载器(下载/解码在网络与工作线程侧,不占这一帧),
|
|
1142
|
+
* 真正写 uniform 的那一步交给 `threeUtils.addWork` —— 全局 rAF 每拍只跑一项 ⇒ 一帧只绑一颗平面,
|
|
1143
|
+
* 一整层平面的纹理上传被摊到多帧,不再挤在同一记调用里(旧写法一记 `feed` 就把所有平面全绑完)。
|
|
1144
|
+
* ⛔ 不进 `onBeforeRender` 自驱;持有渲染循环的那一方(编辑态 / 预览 / 下游)每帧调一次只是「补排漏网的平面」,
|
|
1145
|
+
* 而整层重建(`setImagePlanes`)末尾自己起排 ⇒ 只调 `importData(整串)` 的宿主不必自己驱动也会长出来。
|
|
1146
|
+
* 还没下载完/缺格的那一格绑 null ⇒ 片元那一格整格 discard,不落像素(用户 10-26 连收两条:先「不要默认图」,
|
|
1147
|
+
* 再「不是显示灰色, 而是直接 discard 丢弃」)。
|
|
1100
1148
|
*/
|
|
1101
1149
|
feedImagePlaneTiles() {
|
|
1102
|
-
|
|
1150
|
+
for (const ip of this.imagePlanes)
|
|
1151
|
+
this._queueImagePlaneBind(ip);
|
|
1152
|
+
}
|
|
1153
|
+
/** 一颗平面排一帧的绑定工作:先把它那 4 条地址**一次性全部**交给下载器,再把「写材质」排进全局队列。 */
|
|
1154
|
+
_queueImagePlaneBind(ip) {
|
|
1155
|
+
const tiles = ip.tiles;
|
|
1156
|
+
const sig = (tiles ?? []).join("|");
|
|
1157
|
+
// 两道闸:已排在队里 = 不重复排;这串地址已经落定过 = 不再动它(换图会改这串 ⇒ 自然会重排)
|
|
1158
|
+
if (this._imgBindQueued.has(ip) || this._imgBindDone.get(ip) === sig)
|
|
1103
1159
|
return;
|
|
1104
1160
|
const zero = new Vector4(0, 0, 1, 1);
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1161
|
+
// ⛔ 旧存档 16 条地址(> 满批 4)直接不收 ⇒ 整张不落像素(四格全 discard),按 10-20 口径重传即可;空 tiles 同样
|
|
1162
|
+
if (!tiles || tiles.length !== 4) {
|
|
1163
|
+
ip.setTileTextures([null, null, null, null], [zero, zero.clone(), zero.clone(), zero.clone()]);
|
|
1164
|
+
this._imgBindDone.set(ip, sig);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
for (const url of tiles)
|
|
1168
|
+
this._imgTileLoader.preload(url); // ① 一次性全部拿去加载
|
|
1169
|
+
this._imgBindQueued.add(ip);
|
|
1170
|
+
threeUtils.addWork(() => { }); // 占一格(什么都不干),把下面那一项推到下一个 tick —— 与全景上传同一手法
|
|
1171
|
+
threeUtils.addWork(() => this._bindImagePlaneStep(ip, sig));
|
|
1172
|
+
}
|
|
1173
|
+
/** 队列里的一格:只绑**这一颗**平面里已就绪的那几格;还有在途的就重新排队,⛔ 不在这里等、也⛔ 不顺手把别的平面一起做完。 */
|
|
1174
|
+
_bindImagePlaneStep(ip, sig) {
|
|
1175
|
+
this._imgBindQueued.delete(ip);
|
|
1176
|
+
if (!this.imagePlanes.includes(ip))
|
|
1177
|
+
return; // 已摘掉(删平面 / 切项目整层重建)⇒ 这一格什么都不做
|
|
1178
|
+
const tiles = ip.tiles;
|
|
1179
|
+
if ((tiles ?? []).join("|") !== sig)
|
|
1180
|
+
return; // 地址中途换了 ⇒ 下一记 feed 按新拼串重排
|
|
1181
|
+
const zero = new Vector4(0, 0, 1, 1);
|
|
1182
|
+
const texs = [null, null, null, null];
|
|
1183
|
+
const insets = [zero.clone(), zero.clone(), zero.clone(), zero.clone()];
|
|
1184
|
+
let pending = false;
|
|
1185
|
+
for (let i = 0; i < 4; i++) {
|
|
1186
|
+
const url = tiles[i];
|
|
1187
|
+
if (!url)
|
|
1110
1188
|
continue;
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const insets = [zero.clone(), zero.clone(), zero.clone(), zero.clone()];
|
|
1114
|
-
for (let i = 0; i < 4; i++) {
|
|
1115
|
-
const url = tiles[i];
|
|
1116
|
-
if (!url)
|
|
1117
|
-
continue;
|
|
1118
|
-
this._imgTileLoader.preload(url);
|
|
1119
|
-
const e = this._imgTileLoader.get(url);
|
|
1120
|
-
if (!e || !e.tex)
|
|
1121
|
-
continue;
|
|
1189
|
+
const e = this._imgTileLoader.get(url);
|
|
1190
|
+
if (e && e.tex) {
|
|
1122
1191
|
texs[i] = e.tex;
|
|
1123
1192
|
insets[i] = this._imgTileLoader.inset(e, i);
|
|
1124
1193
|
}
|
|
1125
|
-
|
|
1194
|
+
else if (this._imgTileLoader.isPending(url)) {
|
|
1195
|
+
pending = true; // 还在下载/解码 ⇒ 这一格先绑已到的,剩下的下一拍再看
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
ip.setTileTextures(texs, insets);
|
|
1199
|
+
if (pending) {
|
|
1200
|
+
this._queueImagePlaneBind(ip);
|
|
1201
|
+
return;
|
|
1126
1202
|
}
|
|
1203
|
+
this._imgBindDone.set(ip, sig); // 全部落定(含下载失败那格:它留 null,那一格就永久 discard)
|
|
1127
1204
|
}
|
|
1128
1205
|
/** 导出数据:墙点位 + 墙高 + 是否闭合 + 纹理数组(url + 矩阵)
|
|
1129
1206
|
* @param projName 项目名(可选,优先于 `setProjectName` 注入的那个)——图片地址落成 `{项目名}/xxx.jpg` */
|
|
@@ -1230,7 +1307,7 @@ export default class MktMesh extends Mesh {
|
|
|
1230
1307
|
this._uploadQueued.clear(); // 排队项即便回调仍触发,也会因 `!this._uploadQueued.delete(tex)` 早退
|
|
1231
1308
|
this._ossSizeDepKeys.clear();
|
|
1232
1309
|
// ⭐ 10-26 旨入库的两层:子对象逐个 dispose,平面那 4 张瓦片的缓存**只在这里**整批清
|
|
1233
|
-
// (与上面 `_texCache` 同一条旨 ⇒
|
|
1310
|
+
// (与上面 `_texCache` 同一条旨 ⇒ 切工程不重建这一份、也就不会再空出一格)。
|
|
1234
1311
|
for (const ip of this.imagePlanes) {
|
|
1235
1312
|
this.remove(ip);
|
|
1236
1313
|
ip.dispose();
|
|
@@ -1242,6 +1319,9 @@ export default class MktMesh extends Mesh {
|
|
|
1242
1319
|
}
|
|
1243
1320
|
this.models = [];
|
|
1244
1321
|
this._imgTileLoader.clear();
|
|
1322
|
+
this._imgBindQueued.clear();
|
|
1323
|
+
this._imgBindDone.clear();
|
|
1324
|
+
this._modelMountQueued.clear();
|
|
1245
1325
|
this._stitcher.dispose();
|
|
1246
1326
|
this._glRenderer = null;
|
|
1247
1327
|
if (this._placeholder)
|
package/MktMesh/MktMesh.ts
CHANGED
|
@@ -213,9 +213,23 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
213
213
|
models: ModelSlot[] = [];
|
|
214
214
|
/**
|
|
215
215
|
* 那 4 张瓦片的下载/解码缓存。⭐ 与全景 `_texCache` 同一条旨(10-02「纹理都缓存在对象里面,执行 dispose 时才销毁」)
|
|
216
|
-
* ⇒
|
|
216
|
+
* ⇒ 切工程**不**清它,回来那一圈直接命中缓存、不会再空一格;只随本对象 `dispose()` 释放。
|
|
217
217
|
*/
|
|
218
218
|
private _imgTileLoader = new ImagePlaneTileLoader();
|
|
219
|
+
/**
|
|
220
|
+
* 逐帧绑定用的两张账(2026-10-26 定的方案:地址一次性全部丢给下载,绑材质改成「一帧一颗平面」)。
|
|
221
|
+
* 全局 rAF 每拍只跑一项 `addWork`,而 `addWork` 按闭包引用判重 ⇒ 每次现造的新闭包它判不出来,
|
|
222
|
+
* ⛔ 去重闸门必须像全景上传那张 `_uploadQueued` 一样自备。
|
|
223
|
+
*/
|
|
224
|
+
private _imgBindQueued = new Set<ImagePlane>();
|
|
225
|
+
/** 平面 → 「它 4 条地址全部落定(就绪或失败)时的那串地址」⇒ 地址不换就不再重排;上传换图会改这串 ⇒ 自动重排 */
|
|
226
|
+
private _imgBindDone = new Map<ImagePlane, string>();
|
|
227
|
+
/**
|
|
228
|
+
* 模型槽「GLB 已取回、等这一帧把它挂进父元素」的在册表 —— 与上面图片绑定同一手法(用户 10-26 需求:
|
|
229
|
+
* 一次性全部拿去加载,用 `threeUtils.addWork` 逐帧加到场景里)。整层十几个槽同时到货时,
|
|
230
|
+
* 一帧只有一颗进 `_contentRoot`(进图那一拍要重算包围盒 + 排布 + 首次把几何送上 GPU)。
|
|
231
|
+
*/
|
|
232
|
+
private _modelMountQueued = new Set<ModelSlot>();
|
|
219
233
|
|
|
220
234
|
constructor(opts: { wallHeight?: number; wallClosed?: boolean } = {}) {
|
|
221
235
|
// 发布/预览态墙:**固定**用「全景序号直取」着色器(皇上旨 2026-09-29:mktmesh 就只有按照 attribute
|
|
@@ -1057,10 +1071,11 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1057
1071
|
|
|
1058
1072
|
// ───────────── 平面图 / 模型两层(⭐ 皇上旨 2026-10-26「把图片平面和模型一起搬进库里」) ─────────────
|
|
1059
1073
|
|
|
1060
|
-
/** 单颗图片平面入层(编辑态双击落点用):进数组 + 挂进本对象子树 */
|
|
1074
|
+
/** 单颗图片平面入层(编辑态双击落点用):进数组 + 挂进本对象子树 + 起排它自己的逐帧绑定 */
|
|
1061
1075
|
addImagePlane(ip: ImagePlane): ImagePlane {
|
|
1062
1076
|
this.imagePlanes.push(ip);
|
|
1063
1077
|
this.add(ip);
|
|
1078
|
+
this._queueImagePlaneBind(ip);
|
|
1064
1079
|
return ip;
|
|
1065
1080
|
}
|
|
1066
1081
|
|
|
@@ -1068,6 +1083,8 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1068
1083
|
removeImagePlane(ip: ImagePlane): void {
|
|
1069
1084
|
const i = this.imagePlanes.indexOf(ip);
|
|
1070
1085
|
if (i >= 0) this.imagePlanes.splice(i, 1);
|
|
1086
|
+
this._imgBindQueued.delete(ip);
|
|
1087
|
+
this._imgBindDone.delete(ip);
|
|
1071
1088
|
this.remove(ip);
|
|
1072
1089
|
ip.dispose();
|
|
1073
1090
|
}
|
|
@@ -1079,10 +1096,11 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1079
1096
|
return ms;
|
|
1080
1097
|
}
|
|
1081
1098
|
|
|
1082
|
-
/** 摘掉一槽并释放(子树里的几何/材质/贴图由 `ModelSlot.dispose`
|
|
1099
|
+
/** 摘掉一槽并释放(子树里的几何/材质/贴图由 `ModelSlot.dispose` 自己收尾;排在后头的挂载帧靠「不在册」早退) */
|
|
1083
1100
|
removeModel(ms: ModelSlot): void {
|
|
1084
1101
|
const i = this.models.indexOf(ms);
|
|
1085
1102
|
if (i >= 0) this.models.splice(i, 1);
|
|
1103
|
+
this._modelMountQueued.delete(ms);
|
|
1086
1104
|
this.remove(ms);
|
|
1087
1105
|
ms.dispose();
|
|
1088
1106
|
}
|
|
@@ -1093,6 +1111,8 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1093
1111
|
* ⛔ 本方法里不许再补一遍,也⛔ 不许在调用方各自补。
|
|
1094
1112
|
*/
|
|
1095
1113
|
setImagePlanes(datas: ImagePlaneData[]): void {
|
|
1114
|
+
this._imgBindQueued.clear();
|
|
1115
|
+
this._imgBindDone.clear();
|
|
1096
1116
|
for (const ip of this.imagePlanes) {
|
|
1097
1117
|
this.remove(ip);
|
|
1098
1118
|
ip.dispose();
|
|
@@ -1104,14 +1124,20 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1104
1124
|
this.imagePlanes.push(ip);
|
|
1105
1125
|
this.add(ip);
|
|
1106
1126
|
}
|
|
1127
|
+
// 整层重建完立刻起排:地址一次性全交给下载、绑材质逐帧一记 ⇒ 宿主不驱动这一层也长得出来
|
|
1128
|
+
this.feedImagePlaneTiles();
|
|
1107
1129
|
}
|
|
1108
1130
|
|
|
1109
1131
|
/**
|
|
1110
1132
|
* 整层重建模型槽(`importData` 与预览那一路),地址口径的要求同 `setImagePlanes`。
|
|
1111
1133
|
* ⚠️ GLB 逐只**异步取、不 await**(十几只串一条队会把载入卡死),每只落地那一刻各回调一次 `onEachLoad`:
|
|
1112
1134
|
* 预览每帧真画 ⇒ 不传;编辑器是按需渲染 ⇒ 传一个「标脏」的钩子,否则模型挂上来屏幕不动。
|
|
1135
|
+
* 2026-10-26 决定:取回与「挂进父元素」分两拍 —— 地址一次性全交给下载(网络 + 解析不占渲染帧),
|
|
1136
|
+
* 真正进场景图那一步交给 `threeUtils.addWork` ⇒ 全局 rAF 每拍只跑一项 = **一帧只挂一颗槽**
|
|
1137
|
+
* (挂载那一拍要重算包围盒 + 排布 + 首次把整棵子树的几何送上 GPU,十几颗挤在同一帧就是他要拆掉的那一卡)。
|
|
1113
1138
|
*/
|
|
1114
1139
|
setModels(datas: ModelSlotData[], onEachLoad?: () => void): void {
|
|
1140
|
+
this._modelMountQueued.clear();
|
|
1115
1141
|
for (const ms of this.models) {
|
|
1116
1142
|
this.remove(ms);
|
|
1117
1143
|
ms.dispose();
|
|
@@ -1121,39 +1147,87 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1121
1147
|
const ms = new ModelSlot(d.name);
|
|
1122
1148
|
ms.applyData(d);
|
|
1123
1149
|
this.models.push(ms);
|
|
1124
|
-
this.add(ms);
|
|
1125
|
-
if (d.url) void ms.
|
|
1150
|
+
this.add(ms); // 槽本体(占位小立方体)当场进图 ⇒ GLB 在路上时这一槽不凭空消失
|
|
1151
|
+
if (d.url) void ms.loadModel(d.url).then(() => this._queueModelMount(ms, onEachLoad));
|
|
1126
1152
|
}
|
|
1127
1153
|
}
|
|
1128
1154
|
|
|
1155
|
+
/** 把「备好待挂」的那一槽交给全局队列;⛔ 去重闸门自备(`addWork` 按闭包引用判重,新闭包它判不出来) */
|
|
1156
|
+
private _queueModelMount(ms: ModelSlot, onEachLoad?: () => void): void {
|
|
1157
|
+
if (this._modelMountQueued.has(ms)) return;
|
|
1158
|
+
this._modelMountQueued.add(ms);
|
|
1159
|
+
threeUtils.addWork(() => {});
|
|
1160
|
+
threeUtils.addWork(() => this._mountModelStep(ms, onEachLoad));
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/** 全局队列里的那一帧:只挂这一槽,挂好才回调(`onEachLoad` = 按需渲染宿主的标脏钩子) */
|
|
1164
|
+
private _mountModelStep(ms: ModelSlot, onEachLoad?: () => void): void {
|
|
1165
|
+
this._modelMountQueued.delete(ms);
|
|
1166
|
+
// 槽已退场(换项目 / 删除):待挂那棵由 `ModelSlot.dispose` → `_detachModel` 当场释放,这里不挂
|
|
1167
|
+
if (!this.models.includes(ms)) return;
|
|
1168
|
+
if (ms.mountStaged() && onEachLoad) onEachLoad();
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1129
1171
|
/**
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1172
|
+
* 把各颗平面那 4 张瓦片的纹理绑进材质(10-22 晚定的口径:不走拼合器 RT)。
|
|
1173
|
+
* 2026-10-26 决定改成**逐帧**:一次把整层地址全丢给下载器(下载/解码在网络与工作线程侧,不占这一帧),
|
|
1174
|
+
* 真正写 uniform 的那一步交给 `threeUtils.addWork` —— 全局 rAF 每拍只跑一项 ⇒ 一帧只绑一颗平面,
|
|
1175
|
+
* 一整层平面的纹理上传被摊到多帧,不再挤在同一记调用里(旧写法一记 `feed` 就把所有平面全绑完)。
|
|
1176
|
+
* ⛔ 不进 `onBeforeRender` 自驱;持有渲染循环的那一方(编辑态 / 预览 / 下游)每帧调一次只是「补排漏网的平面」,
|
|
1177
|
+
* 而整层重建(`setImagePlanes`)末尾自己起排 ⇒ 只调 `importData(整串)` 的宿主不必自己驱动也会长出来。
|
|
1178
|
+
* 还没下载完/缺格的那一格绑 null ⇒ 片元那一格整格 discard,不落像素(用户 10-26 连收两条:先「不要默认图」,
|
|
1179
|
+
* 再「不是显示灰色, 而是直接 discard 丢弃」)。
|
|
1133
1180
|
*/
|
|
1134
1181
|
feedImagePlaneTiles(): void {
|
|
1135
|
-
|
|
1182
|
+
for (const ip of this.imagePlanes) this._queueImagePlaneBind(ip);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/** 一颗平面排一帧的绑定工作:先把它那 4 条地址**一次性全部**交给下载器,再把「写材质」排进全局队列。 */
|
|
1186
|
+
private _queueImagePlaneBind(ip: ImagePlane): void {
|
|
1187
|
+
const tiles = ip.tiles;
|
|
1188
|
+
const sig = (tiles ?? []).join("|");
|
|
1189
|
+
// 两道闸:已排在队里 = 不重复排;这串地址已经落定过 = 不再动它(换图会改这串 ⇒ 自然会重排)
|
|
1190
|
+
if (this._imgBindQueued.has(ip) || this._imgBindDone.get(ip) === sig) return;
|
|
1136
1191
|
const zero = new Vector4(0, 0, 1, 1);
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1192
|
+
// ⛔ 旧存档 16 条地址(> 满批 4)直接不收 ⇒ 整张不落像素(四格全 discard),按 10-20 口径重传即可;空 tiles 同样
|
|
1193
|
+
if (!tiles || tiles.length !== 4) {
|
|
1194
|
+
ip.setTileTextures([null, null, null, null], [zero, zero.clone(), zero.clone(), zero.clone()]);
|
|
1195
|
+
this._imgBindDone.set(ip, sig);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
for (const url of tiles) this._imgTileLoader.preload(url); // ① 一次性全部拿去加载
|
|
1199
|
+
this._imgBindQueued.add(ip);
|
|
1200
|
+
threeUtils.addWork(() => {}); // 占一格(什么都不干),把下面那一项推到下一个 tick —— 与全景上传同一手法
|
|
1201
|
+
threeUtils.addWork(() => this._bindImagePlaneStep(ip, sig));
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/** 队列里的一格:只绑**这一颗**平面里已就绪的那几格;还有在途的就重新排队,⛔ 不在这里等、也⛔ 不顺手把别的平面一起做完。 */
|
|
1205
|
+
private _bindImagePlaneStep(ip: ImagePlane, sig: string): void {
|
|
1206
|
+
this._imgBindQueued.delete(ip);
|
|
1207
|
+
if (!this.imagePlanes.includes(ip)) return; // 已摘掉(删平面 / 切项目整层重建)⇒ 这一格什么都不做
|
|
1208
|
+
const tiles = ip.tiles;
|
|
1209
|
+
if ((tiles ?? []).join("|") !== sig) return; // 地址中途换了 ⇒ 下一记 feed 按新拼串重排
|
|
1210
|
+
const zero = new Vector4(0, 0, 1, 1);
|
|
1211
|
+
const texs: (Texture | null)[] = [null, null, null, null];
|
|
1212
|
+
const insets: Vector4[] = [zero.clone(), zero.clone(), zero.clone(), zero.clone()];
|
|
1213
|
+
let pending = false;
|
|
1214
|
+
for (let i = 0; i < 4; i++) {
|
|
1215
|
+
const url = tiles[i];
|
|
1216
|
+
if (!url) continue;
|
|
1217
|
+
const e = this._imgTileLoader.get(url);
|
|
1218
|
+
if (e && e.tex) {
|
|
1152
1219
|
texs[i] = e.tex;
|
|
1153
1220
|
insets[i] = this._imgTileLoader.inset(e, i);
|
|
1221
|
+
} else if (this._imgTileLoader.isPending(url)) {
|
|
1222
|
+
pending = true; // 还在下载/解码 ⇒ 这一格先绑已到的,剩下的下一拍再看
|
|
1154
1223
|
}
|
|
1155
|
-
ip.setTileTextures(texs, insets);
|
|
1156
1224
|
}
|
|
1225
|
+
ip.setTileTextures(texs, insets);
|
|
1226
|
+
if (pending) {
|
|
1227
|
+
this._queueImagePlaneBind(ip);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
this._imgBindDone.set(ip, sig); // 全部落定(含下载失败那格:它留 null,那一格就永久 discard)
|
|
1157
1231
|
}
|
|
1158
1232
|
|
|
1159
1233
|
/** 导出数据:墙点位 + 墙高 + 是否闭合 + 纹理数组(url + 矩阵)
|
|
@@ -1269,7 +1343,7 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1269
1343
|
this._uploadQueued.clear(); // 排队项即便回调仍触发,也会因 `!this._uploadQueued.delete(tex)` 早退
|
|
1270
1344
|
this._ossSizeDepKeys.clear();
|
|
1271
1345
|
// ⭐ 10-26 旨入库的两层:子对象逐个 dispose,平面那 4 张瓦片的缓存**只在这里**整批清
|
|
1272
|
-
// (与上面 `_texCache` 同一条旨 ⇒
|
|
1346
|
+
// (与上面 `_texCache` 同一条旨 ⇒ 切工程不重建这一份、也就不会再空出一格)。
|
|
1273
1347
|
for (const ip of this.imagePlanes) {
|
|
1274
1348
|
this.remove(ip);
|
|
1275
1349
|
ip.dispose();
|
|
@@ -1281,6 +1355,9 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1281
1355
|
}
|
|
1282
1356
|
this.models = [];
|
|
1283
1357
|
this._imgTileLoader.clear();
|
|
1358
|
+
this._imgBindQueued.clear();
|
|
1359
|
+
this._imgBindDone.clear();
|
|
1360
|
+
this._modelMountQueued.clear();
|
|
1284
1361
|
this._stitcher.dispose();
|
|
1285
1362
|
this._glRenderer = null;
|
|
1286
1363
|
if (this._placeholder) this._placeholder.dispose();
|
package/MktMesh/imagePlane.d.ts
CHANGED
|
@@ -46,8 +46,11 @@ export declare class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
46
46
|
get url(): string;
|
|
47
47
|
get naturalW(): number | undefined;
|
|
48
48
|
get naturalH(): number | undefined;
|
|
49
|
-
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒
|
|
49
|
+
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒ 那一片整格 discard,不落像素)。uTiles 用持久对象引用 ⇒ 改 value 实时生效 */
|
|
50
50
|
setTileTextures(texs: (Texture | null)[], insets: Vector4[]): void;
|
|
51
|
+
/** 只读「此刻材质里绑了什么」的戳记:4 格就绪位 + 已绑纹理的版本号。
|
|
52
|
+
* 按需渲染的宿主拿它比对画面:逐帧绑定只写 uniform 值,⛔ 不动 `material.version`、也不动几何 ⇒ 场景指纹看不见这一变。 */
|
|
53
|
+
tilesBindingStamp(): string;
|
|
51
54
|
highlight(): void;
|
|
52
55
|
restoreColor(): void;
|
|
53
56
|
toData(): ImagePlaneData;
|
package/MktMesh/imagePlane.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* - 几何 PlaneGeometry(width, height),原点在平面中心;position.y 由调用者设定(通常 height/2 让底部落地)。
|
|
12
12
|
* - ⭐ 2026-10-22 晚旨:纹理**不走拼合器 RT**,直接把上传切好的 4 张瓦片纹理绑到材质,片元按 UV 象限选片 +
|
|
13
13
|
* 用每片 `rtStitchTileInset` 窗口采样(与上传切片共用同一份几何 ⇒ 接缝不回潮)。材质用 onBeforeCompile 打补丁,
|
|
14
|
-
* 注入 4 个 sampler + 每片 inset +
|
|
15
|
-
* (⭐
|
|
14
|
+
* 注入 4 个 sampler + 每片 inset + 就绪标志;缺失那片整格 `discard`(一片像素都不落),逐张就绪逐张显影。
|
|
15
|
+
* (⭐ 2026-10-26 用户先要默认图、当晚收回改要灰,同一晚再收:「平面图的图片还没有加载出来的时候, 不是显示灰色,
|
|
16
|
+
* 而是直接 discrad 丢弃」⇒ 默认图与缺图灰两条都已删,没就绪就是透明没有。)
|
|
16
17
|
* - UV 标准 [0,1]²(PlaneGeometry 自带),4 张瓦片按 2×2 象限铺满 ⇒ 不需要投影 shader,也不需要离屏 RT。
|
|
17
18
|
*/
|
|
18
19
|
import { Color, DataTexture, FrontSide, Mesh, MeshBasicMaterial, PlaneGeometry, RGBAFormat, Vector4, } from "three";
|
|
@@ -36,21 +37,25 @@ const ZERO_INSET = new Vector4(0, 0, 1, 1);
|
|
|
36
37
|
*/
|
|
37
38
|
export const IMAGE_PLANE_EDGE_CLIP_UV = 0.004;
|
|
38
39
|
const EDGE_MAX_UV = 1 - IMAGE_PLANE_EDGE_CLIP_UV;
|
|
39
|
-
/** 片元打补丁:4 张瓦片按 UV
|
|
40
|
+
/** 片元打补丁:4 张瓦片按 UV 象限选片;⭐ 2026-10-26 用户决定「图片还没有加载出来的时候, 不是显示灰色, 而是直接 discrad 丢弃」
|
|
41
|
+
* ⇒ 这一格没就绪就 `discard`(一片像素都不落,透过去看到的是平面背后的东西),⛔ 不铺灰、⛔ 不画默认图。
|
|
42
|
+
* 索引用字面量 0..3 ⇒ GLSL ES 1.00 合法。 */
|
|
40
43
|
const IMG_PLANE_MAP_FRAG = `
|
|
41
44
|
if ( vUv.x < ${IMAGE_PLANE_EDGE_CLIP_UV} || vUv.x > ${EDGE_MAX_UV} || vUv.y < ${IMAGE_PLANE_EDGE_CLIP_UV} || vUv.y > ${EDGE_MAX_UV} ) discard;
|
|
42
|
-
vec4 imgTexel
|
|
45
|
+
vec4 imgTexel;
|
|
46
|
+
bool ready = false;
|
|
43
47
|
vec2 cell = floor(vUv * 2.0);
|
|
44
48
|
vec2 local = fract(vUv * 2.0);
|
|
45
49
|
if (cell.y >= 0.5 && cell.x < 0.5) {
|
|
46
|
-
if (uValid[0] > 0.5) { vec4 ins = uInset[0]; imgTexel = texture2D(uTiles[0], mix(ins.xy, ins.zw, local)); }
|
|
50
|
+
if (uValid[0] > 0.5) { vec4 ins = uInset[0]; imgTexel = texture2D(uTiles[0], mix(ins.xy, ins.zw, local)); ready = true; }
|
|
47
51
|
} else if (cell.y >= 0.5 && cell.x >= 0.5) {
|
|
48
|
-
if (uValid[1] > 0.5) { vec4 ins = uInset[1]; imgTexel = texture2D(uTiles[1], mix(ins.xy, ins.zw, local)); }
|
|
52
|
+
if (uValid[1] > 0.5) { vec4 ins = uInset[1]; imgTexel = texture2D(uTiles[1], mix(ins.xy, ins.zw, local)); ready = true; }
|
|
49
53
|
} else if (cell.y < 0.5 && cell.x < 0.5) {
|
|
50
|
-
if (uValid[2] > 0.5) { vec4 ins = uInset[2]; imgTexel = texture2D(uTiles[2], mix(ins.xy, ins.zw, local)); }
|
|
54
|
+
if (uValid[2] > 0.5) { vec4 ins = uInset[2]; imgTexel = texture2D(uTiles[2], mix(ins.xy, ins.zw, local)); ready = true; }
|
|
51
55
|
} else {
|
|
52
|
-
if (uValid[3] > 0.5) { vec4 ins = uInset[3]; imgTexel = texture2D(uTiles[3], mix(ins.xy, ins.zw, local)); }
|
|
56
|
+
if (uValid[3] > 0.5) { vec4 ins = uInset[3]; imgTexel = texture2D(uTiles[3], mix(ins.xy, ins.zw, local)); ready = true; }
|
|
53
57
|
}
|
|
58
|
+
if (!ready) discard;
|
|
54
59
|
diffuseColor *= imgTexel;
|
|
55
60
|
`;
|
|
56
61
|
const DEFAULT_WIDTH = 2;
|
|
@@ -79,7 +84,7 @@ export class ImagePlane extends Mesh {
|
|
|
79
84
|
});
|
|
80
85
|
// ⭐ 2026-10-22 晚旨:图片平面**不用拼合器 RT**,直接把上传切好的 4 张瓦片纹理绑到材质,
|
|
81
86
|
// 片元按 UV 象限选片 + 用每片 `rtStitchTileInset` 窗口采样(与上传切片共用同一份几何 ⇒ 接缝不回潮)。
|
|
82
|
-
// 材质打补丁(onBeforeCompile)注入 4 个 sampler + 每片 inset +
|
|
87
|
+
// 材质打补丁(onBeforeCompile)注入 4 个 sampler + 每片 inset + 就绪标志;缺失那片整格 discard。
|
|
83
88
|
mat.defines = { USE_UV: "" };
|
|
84
89
|
const uTiles = { value: [PLACEHOLDER_TEX, PLACEHOLDER_TEX, PLACEHOLDER_TEX, PLACEHOLDER_TEX] };
|
|
85
90
|
const uInset = { value: [ZERO_INSET.clone(), ZERO_INSET.clone(), ZERO_INSET.clone(), ZERO_INSET.clone()] };
|
|
@@ -114,7 +119,7 @@ export class ImagePlane extends Mesh {
|
|
|
114
119
|
get url() { return this._data.url; }
|
|
115
120
|
get naturalW() { return this._data.naturalW; }
|
|
116
121
|
get naturalH() { return this._data.naturalH; }
|
|
117
|
-
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒
|
|
122
|
+
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒ 那一片整格 discard,不落像素)。uTiles 用持久对象引用 ⇒ 改 value 实时生效 */
|
|
118
123
|
setTileTextures(texs, insets) {
|
|
119
124
|
for (let i = 0; i < 4; i++) {
|
|
120
125
|
const t = texs[i] || null;
|
|
@@ -124,6 +129,16 @@ export class ImagePlane extends Mesh {
|
|
|
124
129
|
this._uValid.value[i] = t ? 1 : 0;
|
|
125
130
|
}
|
|
126
131
|
}
|
|
132
|
+
/** 只读「此刻材质里绑了什么」的戳记:4 格就绪位 + 已绑纹理的版本号。
|
|
133
|
+
* 按需渲染的宿主拿它比对画面:逐帧绑定只写 uniform 值,⛔ 不动 `material.version`、也不动几何 ⇒ 场景指纹看不见这一变。 */
|
|
134
|
+
tilesBindingStamp() {
|
|
135
|
+
let s = "";
|
|
136
|
+
for (let i = 0; i < 4; i++) {
|
|
137
|
+
const t = this._uTiles.value[i];
|
|
138
|
+
s += this._uValid.value[i] ? `1@${t ? t.version : -1},` : "0,";
|
|
139
|
+
}
|
|
140
|
+
return s;
|
|
141
|
+
}
|
|
127
142
|
highlight() {
|
|
128
143
|
this.material.color.set(HIGHLIGHT_COLOR);
|
|
129
144
|
}
|