three-base 2.0.197 → 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 +52 -8
- package/MktMesh/MktMesh.js +104 -13
- package/MktMesh/MktMesh.ts +106 -14
- package/MktMesh/imagePlane.d.ts +14 -0
- package/MktMesh/imagePlane.js +79 -3
- package/MktMesh/imagePlane.ts +79 -2
- package/MktMesh/mktResManager.d.ts +77 -0
- package/MktMesh/mktResManager.js +89 -0
- package/MktMesh/mktResManager.ts +130 -0
- package/MktMesh/modelSlot.d.ts +2 -0
- package/MktMesh/modelSlot.js +12 -0
- package/MktMesh/modelSlot.ts +12 -0
- package/ThreeBase.js +1 -10
- package/ThreeBase.ts +1 -9
- package/package.json +1 -1
- package/pass/debugDepthState.d.ts +4 -8
- package/pass/debugDepthState.js +0 -4
- package/pass/debugDepthState.ts +4 -10
- package/pass/stroke_pass.js +8 -90
- package/pass/stroke_pass.ts +8 -94
package/MktMesh/imagePlane.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ export declare class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
39
39
|
private _data;
|
|
40
40
|
/** 内容是否已落定:4 格里至少有一格真的绑上了纹理。**只朝 true 翻一次**(之后逐张显影继续,但不再回退成「没内容」)。 */
|
|
41
41
|
private _loaded;
|
|
42
|
+
/** 此刻是不是编辑器的选中态(决定上不上黄、边框亮不亮) */
|
|
43
|
+
private _selected;
|
|
44
|
+
/** 选中边框:懒建(预览/发布那一路从不选中 ⇒ 一颗都不建) */
|
|
45
|
+
private _outline;
|
|
42
46
|
constructor(name?: string);
|
|
43
47
|
get imageWidth(): number;
|
|
44
48
|
get imageHeight(): number;
|
|
@@ -57,6 +61,16 @@ export declare class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
57
61
|
tilesBindingStamp(): string;
|
|
58
62
|
highlight(): void;
|
|
59
63
|
restoreColor(): void;
|
|
64
|
+
/**
|
|
65
|
+
* 选中态的三样东西一起算(颜色 + 边框),一处规则:
|
|
66
|
+
* - 黄只在**还没上传图**那一档:片元里贴图是乘进 diffuse 的(`diffuseColor *= imgTexel`),有图还套黄 ⇒ 整张图发黄。
|
|
67
|
+
* - 边框在有图/没图两档都亮:没图那四格是整格 `discard`(一个像素都不落),只靠黄等于没有反馈,看不见也找不着。
|
|
68
|
+
*/
|
|
69
|
+
private _syncSelection;
|
|
70
|
+
/** 选中边框懒建:预览/发布那一路从不选中 ⇒ 一颗 LineSegments2 都不建(与模型槽那圈 `BoxLine2` 同口径) */
|
|
71
|
+
private _ensureOutline;
|
|
72
|
+
/** 四条边按当前几何的宽高铺在平面那一平面上(局部 z=0,中心为原点) */
|
|
73
|
+
private _layoutOutline;
|
|
60
74
|
toData(): ImagePlaneData;
|
|
61
75
|
applyData(d: ImagePlaneData): void;
|
|
62
76
|
dispose(): void;
|
package/MktMesh/imagePlane.js
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* 而是直接 discrad 丢弃」⇒ 默认图与缺图灰两条都已删,没就绪就是透明没有。)
|
|
17
17
|
* - UV 标准 [0,1]²(PlaneGeometry 自带),4 张瓦片按 2×2 象限铺满 ⇒ 不需要投影 shader,也不需要离屏 RT。
|
|
18
18
|
*/
|
|
19
|
-
import { Color, DataTexture, FrontSide, Mesh, MeshBasicMaterial, PlaneGeometry, RGBAFormat, Vector4, } from "three";
|
|
19
|
+
import { Color, DataTexture, FrontSide, Mesh, MeshBasicMaterial, PlaneGeometry, RGBAFormat, Vector2, Vector4, } from "three";
|
|
20
|
+
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
|
|
21
|
+
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
|
|
22
|
+
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
|
|
20
23
|
/*
|
|
21
24
|
* ⭐ 2026-10-22 晚旨:图片平面**不用拼合器 RT**,直接把上传切好的 4 张瓦片纹理绑到材质,
|
|
22
25
|
* 片元按 UV 象限选片 + 用每片 `rtStitchTileInset` 窗口采样(与上传切片共用同一份几何 ⇒ 接缝不回潮)。
|
|
@@ -61,6 +64,9 @@ const IMG_PLANE_MAP_FRAG = `
|
|
|
61
64
|
const DEFAULT_WIDTH = 2;
|
|
62
65
|
const DEFAULT_HEIGHT = 1.5;
|
|
63
66
|
const HIGHLIGHT_COLOR = "#ffd24a";
|
|
67
|
+
/** 选中边框:颜色/粗细与模型槽那圈 `BoxLine2` 的朝向相机的棱同一档(0x00ff00 / 2 屏幕像素) */
|
|
68
|
+
const SELECT_LINE_COLOR = 0x00ff00;
|
|
69
|
+
const SELECT_LINE_WIDTH_PX = 2;
|
|
64
70
|
export class ImagePlane extends Mesh {
|
|
65
71
|
_originalColor = "#ffffff";
|
|
66
72
|
_uTiles;
|
|
@@ -72,6 +78,10 @@ export class ImagePlane extends Mesh {
|
|
|
72
78
|
};
|
|
73
79
|
/** 内容是否已落定:4 格里至少有一格真的绑上了纹理。**只朝 true 翻一次**(之后逐张显影继续,但不再回退成「没内容」)。 */
|
|
74
80
|
_loaded = false;
|
|
81
|
+
/** 此刻是不是编辑器的选中态(决定上不上黄、边框亮不亮) */
|
|
82
|
+
_selected = false;
|
|
83
|
+
/** 选中边框:懒建(预览/发布那一路从不选中 ⇒ 一颗都不建) */
|
|
84
|
+
_outline = null;
|
|
75
85
|
constructor(name) {
|
|
76
86
|
const geo = new PlaneGeometry(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
77
87
|
const mat = new MeshBasicMaterial({
|
|
@@ -115,6 +125,7 @@ export class ImagePlane extends Mesh {
|
|
|
115
125
|
setPlaneSize(w, h) {
|
|
116
126
|
this.geometry.dispose();
|
|
117
127
|
this.geometry = new PlaneGeometry(w, h);
|
|
128
|
+
this._layoutOutline(); // 边框跟着宽高走,否则面板改完尺寸框还停在旧那一圈
|
|
118
129
|
}
|
|
119
130
|
setPlaneData(d) {
|
|
120
131
|
this._data = { url: d.url, tiles: d.tiles, naturalW: d.naturalW, naturalH: d.naturalH };
|
|
@@ -125,6 +136,7 @@ export class ImagePlane extends Mesh {
|
|
|
125
136
|
get naturalH() { return this._data.naturalH; }
|
|
126
137
|
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒ 那一片整格 discard,不落像素)。uTiles 用持久对象引用 ⇒ 改 value 实时生效 */
|
|
127
138
|
setTileTextures(texs, insets) {
|
|
139
|
+
const hadContent = this._loaded;
|
|
128
140
|
for (let i = 0; i < 4; i++) {
|
|
129
141
|
const t = texs[i] || null;
|
|
130
142
|
this._uTiles.value[i] = t || PLACEHOLDER_TEX;
|
|
@@ -136,6 +148,10 @@ export class ImagePlane extends Mesh {
|
|
|
136
148
|
if (t)
|
|
137
149
|
this._loaded = true;
|
|
138
150
|
}
|
|
151
|
+
// 上传前这颗是选中态(黄),第一张图一到就把黄摘掉 ⇒ 否则「有图不上色」只在再次选中时才生效,
|
|
152
|
+
// 而他报的正是「上传后」那一刻。
|
|
153
|
+
if (!hadContent && this._loaded)
|
|
154
|
+
this._syncSelection();
|
|
139
155
|
}
|
|
140
156
|
/** 只读「此刻材质里绑了什么」的戳记:4 格就绪位 + 已绑纹理的版本号。
|
|
141
157
|
* 按需渲染的宿主拿它比对画面:逐帧绑定只写 uniform 值,⛔ 不动 `material.version`、也不动几何 ⇒ 场景指纹看不见这一变。 */
|
|
@@ -148,10 +164,66 @@ export class ImagePlane extends Mesh {
|
|
|
148
164
|
return s;
|
|
149
165
|
}
|
|
150
166
|
highlight() {
|
|
151
|
-
this.
|
|
167
|
+
this._selected = true;
|
|
168
|
+
this._syncSelection();
|
|
152
169
|
}
|
|
153
170
|
restoreColor() {
|
|
154
|
-
this.
|
|
171
|
+
this._selected = false;
|
|
172
|
+
this._syncSelection();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 选中态的三样东西一起算(颜色 + 边框),一处规则:
|
|
176
|
+
* - 黄只在**还没上传图**那一档:片元里贴图是乘进 diffuse 的(`diffuseColor *= imgTexel`),有图还套黄 ⇒ 整张图发黄。
|
|
177
|
+
* - 边框在有图/没图两档都亮:没图那四格是整格 `discard`(一个像素都不落),只靠黄等于没有反馈,看不见也找不着。
|
|
178
|
+
*/
|
|
179
|
+
_syncSelection() {
|
|
180
|
+
this.material.color.set(this._selected && !this._loaded ? HIGHLIGHT_COLOR : this._originalColor);
|
|
181
|
+
if (!this._selected) {
|
|
182
|
+
if (this._outline)
|
|
183
|
+
this._outline.visible = false;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this._ensureOutline();
|
|
187
|
+
this._layoutOutline();
|
|
188
|
+
}
|
|
189
|
+
/** 选中边框懒建:预览/发布那一路从不选中 ⇒ 一颗 LineSegments2 都不建(与模型槽那圈 `BoxLine2` 同口径) */
|
|
190
|
+
_ensureOutline() {
|
|
191
|
+
if (this._outline)
|
|
192
|
+
return;
|
|
193
|
+
const material = new LineMaterial({
|
|
194
|
+
color: SELECT_LINE_COLOR,
|
|
195
|
+
worldUnits: false,
|
|
196
|
+
linewidth: SELECT_LINE_WIDTH_PX,
|
|
197
|
+
// ⚠️ `depthTest: false`:这一圈就躺在平面自己那一平面上,留着深度测试必与图打摆(闪烁)
|
|
198
|
+
depthTest: false,
|
|
199
|
+
resolution: new Vector2(1, 1),
|
|
200
|
+
});
|
|
201
|
+
const outline = new LineSegments2(new LineSegmentsGeometry(), material);
|
|
202
|
+
outline.name = `${this.name}-选中边框`;
|
|
203
|
+
outline.renderOrder = 100; // 与 `BoxLine2` 那圈同档:压在图/墙之后
|
|
204
|
+
outline.frustumCulled = false;
|
|
205
|
+
outline.userData.pickIgnored = true;
|
|
206
|
+
outline.raycast = () => { }; // 拾取打平面本体,边框不是可点的东西
|
|
207
|
+
outline.onBeforeRender = (renderer) => {
|
|
208
|
+
renderer.getSize(material.resolution); // 屏幕像素粗细要每帧拿画布尺寸
|
|
209
|
+
};
|
|
210
|
+
this.add(outline);
|
|
211
|
+
this._outline = outline;
|
|
212
|
+
}
|
|
213
|
+
/** 四条边按当前几何的宽高铺在平面那一平面上(局部 z=0,中心为原点) */
|
|
214
|
+
_layoutOutline() {
|
|
215
|
+
if (!this._outline)
|
|
216
|
+
return;
|
|
217
|
+
const w = this.imageWidth / 2;
|
|
218
|
+
const h = this.imageHeight / 2;
|
|
219
|
+
const c = [
|
|
220
|
+
[-w, -h, 0], [w, -h, 0],
|
|
221
|
+
[w, -h, 0], [w, h, 0],
|
|
222
|
+
[w, h, 0], [-w, h, 0],
|
|
223
|
+
[-w, h, 0], [-w, -h, 0],
|
|
224
|
+
].flat();
|
|
225
|
+
this._outline.geometry.setPositions(c);
|
|
226
|
+
this._outline.visible = true;
|
|
155
227
|
}
|
|
156
228
|
toData() {
|
|
157
229
|
return {
|
|
@@ -177,6 +249,10 @@ export class ImagePlane extends Mesh {
|
|
|
177
249
|
dispose() {
|
|
178
250
|
this.geometry.dispose();
|
|
179
251
|
this.material.dispose();
|
|
252
|
+
if (this._outline) {
|
|
253
|
+
this._outline.geometry.dispose();
|
|
254
|
+
this._outline.material.dispose();
|
|
255
|
+
}
|
|
180
256
|
}
|
|
181
257
|
}
|
|
182
258
|
export const IMAGE_PLANE_DEFAULTS = { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
package/MktMesh/imagePlane.ts
CHANGED
|
@@ -25,8 +25,13 @@ import {
|
|
|
25
25
|
PlaneGeometry,
|
|
26
26
|
RGBAFormat,
|
|
27
27
|
Texture,
|
|
28
|
+
Vector2,
|
|
28
29
|
Vector4,
|
|
30
|
+
WebGLRenderer,
|
|
29
31
|
} from "three";
|
|
32
|
+
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
|
|
33
|
+
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
|
|
34
|
+
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
|
|
30
35
|
|
|
31
36
|
/*
|
|
32
37
|
* ⭐ 2026-10-22 晚旨:图片平面**不用拼合器 RT**,直接把上传切好的 4 张瓦片纹理绑到材质,
|
|
@@ -92,6 +97,9 @@ export interface ImagePlaneData {
|
|
|
92
97
|
const DEFAULT_WIDTH = 2;
|
|
93
98
|
const DEFAULT_HEIGHT = 1.5;
|
|
94
99
|
const HIGHLIGHT_COLOR = "#ffd24a";
|
|
100
|
+
/** 选中边框:颜色/粗细与模型槽那圈 `BoxLine2` 的朝向相机的棱同一档(0x00ff00 / 2 屏幕像素) */
|
|
101
|
+
const SELECT_LINE_COLOR = 0x00ff00;
|
|
102
|
+
const SELECT_LINE_WIDTH_PX = 2;
|
|
95
103
|
|
|
96
104
|
export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
97
105
|
private _originalColor: string = "#ffffff";
|
|
@@ -104,6 +112,10 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
104
112
|
};
|
|
105
113
|
/** 内容是否已落定:4 格里至少有一格真的绑上了纹理。**只朝 true 翻一次**(之后逐张显影继续,但不再回退成「没内容」)。 */
|
|
106
114
|
private _loaded = false;
|
|
115
|
+
/** 此刻是不是编辑器的选中态(决定上不上黄、边框亮不亮) */
|
|
116
|
+
private _selected = false;
|
|
117
|
+
/** 选中边框:懒建(预览/发布那一路从不选中 ⇒ 一颗都不建) */
|
|
118
|
+
private _outline: LineSegments2 | null = null;
|
|
107
119
|
|
|
108
120
|
constructor(name?: string) {
|
|
109
121
|
const geo = new PlaneGeometry(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
@@ -153,6 +165,7 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
153
165
|
setPlaneSize(w: number, h: number): void {
|
|
154
166
|
this.geometry.dispose();
|
|
155
167
|
this.geometry = new PlaneGeometry(w, h);
|
|
168
|
+
this._layoutOutline(); // 边框跟着宽高走,否则面板改完尺寸框还停在旧那一圈
|
|
156
169
|
}
|
|
157
170
|
|
|
158
171
|
setPlaneData(d: Pick<ImagePlaneData, "url" | "tiles" | "naturalW" | "naturalH">): void {
|
|
@@ -166,6 +179,7 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
166
179
|
|
|
167
180
|
/** 把上传切好的 4 张瓦片纹理 + 每片 inset 绑到材质(缺图那片传 null ⇒ 那一片整格 discard,不落像素)。uTiles 用持久对象引用 ⇒ 改 value 实时生效 */
|
|
168
181
|
setTileTextures(texs: (Texture | null)[], insets: Vector4[]): void {
|
|
182
|
+
const hadContent = this._loaded;
|
|
169
183
|
for (let i = 0; i < 4; i++) {
|
|
170
184
|
const t = texs[i] || null;
|
|
171
185
|
this._uTiles.value[i] = t || PLACEHOLDER_TEX;
|
|
@@ -175,6 +189,9 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
175
189
|
// 「逐张就绪逐张显影」是本文件 10-22 晚定下的口径,等到全到齐会让先到的那几格白等几帧)。
|
|
176
190
|
if (t) this._loaded = true;
|
|
177
191
|
}
|
|
192
|
+
// 上传前这颗是选中态(黄),第一张图一到就把黄摘掉 ⇒ 否则「有图不上色」只在再次选中时才生效,
|
|
193
|
+
// 而他报的正是「上传后」那一刻。
|
|
194
|
+
if (!hadContent && this._loaded) this._syncSelection();
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
/** 只读「此刻材质里绑了什么」的戳记:4 格就绪位 + 已绑纹理的版本号。
|
|
@@ -189,11 +206,67 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
189
206
|
}
|
|
190
207
|
|
|
191
208
|
highlight(): void {
|
|
192
|
-
this.
|
|
209
|
+
this._selected = true;
|
|
210
|
+
this._syncSelection();
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
restoreColor(): void {
|
|
196
|
-
this.
|
|
214
|
+
this._selected = false;
|
|
215
|
+
this._syncSelection();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 选中态的三样东西一起算(颜色 + 边框),一处规则:
|
|
220
|
+
* - 黄只在**还没上传图**那一档:片元里贴图是乘进 diffuse 的(`diffuseColor *= imgTexel`),有图还套黄 ⇒ 整张图发黄。
|
|
221
|
+
* - 边框在有图/没图两档都亮:没图那四格是整格 `discard`(一个像素都不落),只靠黄等于没有反馈,看不见也找不着。
|
|
222
|
+
*/
|
|
223
|
+
private _syncSelection(): void {
|
|
224
|
+
this.material.color.set(this._selected && !this._loaded ? HIGHLIGHT_COLOR : this._originalColor);
|
|
225
|
+
if (!this._selected) {
|
|
226
|
+
if (this._outline) this._outline.visible = false;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this._ensureOutline();
|
|
230
|
+
this._layoutOutline();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** 选中边框懒建:预览/发布那一路从不选中 ⇒ 一颗 LineSegments2 都不建(与模型槽那圈 `BoxLine2` 同口径) */
|
|
234
|
+
private _ensureOutline(): void {
|
|
235
|
+
if (this._outline) return;
|
|
236
|
+
const material = new LineMaterial({
|
|
237
|
+
color: SELECT_LINE_COLOR,
|
|
238
|
+
worldUnits: false,
|
|
239
|
+
linewidth: SELECT_LINE_WIDTH_PX,
|
|
240
|
+
// ⚠️ `depthTest: false`:这一圈就躺在平面自己那一平面上,留着深度测试必与图打摆(闪烁)
|
|
241
|
+
depthTest: false,
|
|
242
|
+
resolution: new Vector2(1, 1),
|
|
243
|
+
});
|
|
244
|
+
const outline = new LineSegments2(new LineSegmentsGeometry(), material);
|
|
245
|
+
outline.name = `${this.name}-选中边框`;
|
|
246
|
+
outline.renderOrder = 100; // 与 `BoxLine2` 那圈同档:压在图/墙之后
|
|
247
|
+
outline.frustumCulled = false;
|
|
248
|
+
outline.userData.pickIgnored = true;
|
|
249
|
+
outline.raycast = (): void => { }; // 拾取打平面本体,边框不是可点的东西
|
|
250
|
+
outline.onBeforeRender = (renderer: WebGLRenderer): void => {
|
|
251
|
+
renderer.getSize(material.resolution); // 屏幕像素粗细要每帧拿画布尺寸
|
|
252
|
+
};
|
|
253
|
+
this.add(outline);
|
|
254
|
+
this._outline = outline;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 四条边按当前几何的宽高铺在平面那一平面上(局部 z=0,中心为原点) */
|
|
258
|
+
private _layoutOutline(): void {
|
|
259
|
+
if (!this._outline) return;
|
|
260
|
+
const w = this.imageWidth / 2;
|
|
261
|
+
const h = this.imageHeight / 2;
|
|
262
|
+
const c = [
|
|
263
|
+
[-w, -h, 0], [w, -h, 0],
|
|
264
|
+
[w, -h, 0], [w, h, 0],
|
|
265
|
+
[w, h, 0], [-w, h, 0],
|
|
266
|
+
[-w, h, 0], [-w, -h, 0],
|
|
267
|
+
].flat();
|
|
268
|
+
this._outline.geometry.setPositions(c);
|
|
269
|
+
this._outline.visible = true;
|
|
197
270
|
}
|
|
198
271
|
|
|
199
272
|
toData(): ImagePlaneData {
|
|
@@ -221,6 +294,10 @@ export class ImagePlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {
|
|
|
221
294
|
dispose(): void {
|
|
222
295
|
this.geometry.dispose();
|
|
223
296
|
this.material.dispose();
|
|
297
|
+
if (this._outline) {
|
|
298
|
+
this._outline.geometry.dispose();
|
|
299
|
+
this._outline.material.dispose();
|
|
300
|
+
}
|
|
224
301
|
}
|
|
225
302
|
}
|
|
226
303
|
|
|
@@ -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
|
+
}
|
package/MktMesh/modelSlot.d.ts
CHANGED
package/MktMesh/modelSlot.js
CHANGED
|
@@ -32,6 +32,10 @@ import { Box3, BoxGeometry, Color, Group, Mesh, MeshBasicMaterial, Vector3, } fr
|
|
|
32
32
|
import threeLoader from "../threeLoader";
|
|
33
33
|
import threeUtils from "../threeUtils";
|
|
34
34
|
import BoxLine2 from "./boxLine2";
|
|
35
|
+
/** glb 地址 → 文件名(日志唯一口径:去掉查询串取最后一段;⛔ 别在打印点各写一份 split) */
|
|
36
|
+
export function glbFileName(url) {
|
|
37
|
+
return url.split("?")[0].split("/").pop() || url;
|
|
38
|
+
}
|
|
35
39
|
/** 占位小立方体的棱长(米)—— 皇上要的是「小立方体」,比「加立方体」那颗 1m 的单位方块小一档 */
|
|
36
40
|
export const MODEL_SLOT_PLACEHOLDER = 0.3;
|
|
37
41
|
/** 默认尺寸档(米,最长边):还没上传模型时占位方块用不上它;上传成功后 `useNativeSize()` 会把它填成模型原最长边 ⇒ 一比一 */
|
|
@@ -179,6 +183,9 @@ export class ModelSlot extends Group {
|
|
|
179
183
|
this._loaded = true;
|
|
180
184
|
return true;
|
|
181
185
|
}
|
|
186
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 这一颗的取回入口⛔ 不出声:整层重建一次几十颗,逐颗一行会刷屏。
|
|
187
|
+
// 文件名改由两个批次入口各打一条 —— 单颗动作(上传/复制/换地址)在下面的 `setModelUrl`,
|
|
188
|
+
// 整层重建在 `MktMesh.setModels`(那边一条列全)⇒ 一颗不落,但一拍一条。
|
|
182
189
|
// 取回失败(404 / 解析报错都从这一档走):`threeLoader.loadModel` 是会 reject 的,⚠️ 必须当「落定在占位方块」处理
|
|
183
190
|
// —— 整层重建时这一颗是压着不亮的,不落定就永远隐形(旧写法里它至少还露得出占位方块)。
|
|
184
191
|
let obj;
|
|
@@ -224,6 +231,11 @@ export class ModelSlot extends Group {
|
|
|
224
231
|
* @returns 是否真的挂上了(地址无效/载入失败/这次结果已作废 ⇒ false;空地址 = 退回占位,返回 true)
|
|
225
232
|
*/
|
|
226
233
|
async setModelUrl(url) {
|
|
234
|
+
// 用户 2026-11-04「日志少打印点吧」⇒ 逐颗一行从 `loadModel` 挪到这里:这一路一次动作只动一/几颗
|
|
235
|
+
// (上传 .glb、复制模型、换同名 glb),一条就是他能对上号的那一颗;整层重建那几十颗由
|
|
236
|
+
// `MktMesh.setModels` 攒成一条 ⇒ 文件名仍「一颗不落」,行数从 N 降到 1。空地址 = 退回占位,不打。
|
|
237
|
+
if (url)
|
|
238
|
+
console.log(`[MktMesh] 加载 glb「${this.name}」:${glbFileName(url)}`);
|
|
227
239
|
if (!(await this.loadModel(url)))
|
|
228
240
|
return false;
|
|
229
241
|
if (this._staged)
|