three-base 2.0.175 → 2.0.180
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/boxLine2.d.ts +42 -0
- package/MktMesh/boxLine2.js +185 -0
- package/MktMesh/boxLine2.ts +222 -0
- package/MktMesh/imagePlane.d.ts +60 -0
- package/MktMesh/imagePlane.js +158 -0
- package/MktMesh/imagePlane.ts +203 -0
- package/MktMesh/imageTileLoader.d.ts +19 -0
- package/MktMesh/imageTileLoader.js +55 -0
- package/MktMesh/imageTileLoader.ts +64 -0
- package/MktMesh/modelSlot.d.ts +123 -0
- package/MktMesh/modelSlot.js +339 -0
- package/MktMesh/modelSlot.ts +383 -0
- package/MktMesh/panoSlotRoster.js +1 -1
- package/MktMesh/panoSlotRoster.ts +1 -1
- package/package.json +21 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Description: 模型槽元素 —— 皇上旨 2026-10-20「在 mktmesh 里面添加模型,可以在地面和墙面添加模型,
|
|
3
|
+
* 在初始添加的时候,没有模型就显示成一个小立方体,然后选中小立方体的时候,在编辑弹窗里面可以上传 glb 模型」。
|
|
4
|
+
*
|
|
5
|
+
* 设计(每条都是被现实逼出来的,别顺手简化):
|
|
6
|
+
* - 独立 Group,⛔ 不走 `InstancedChild`:合批的键来自**类级** `static geometry/material`,而 GLB 每个模型
|
|
7
|
+
* 自带一份几何与材质 ⇒ 没有可共用的那一份;且 `InstancedChild` 的选中代理在「类没有 static geometry」时直接跳过。
|
|
8
|
+
* - 两级内容:占位小立方体(未上传时可见,皇上要的那个「小立方体」)+ `modelRoot`(GLB 挂这里)。
|
|
9
|
+
* - ⭐ 皇上旨 2026-10-21「上传模型的时候,不要去修改模型的大小」:上传挂上来的 GLB 保持**它自己的原尺寸**落地。
|
|
10
|
+
* 做法 = 载入成功后由上传入口调一次 `useNativeSize()`,把「尺寸 (m,最长边)」那一栏填成量到的原最长边
|
|
11
|
+
* ⇒ 后面那步归一算出的倍率正好是 1(`scaleTo` 吃的是同一份包围盒数字),画面上一比一、⛔ 不再把一只 20 m 的
|
|
12
|
+
* 展柜压成默认的 1 m。要改大小仍然可以在面板里改那一栏(存档/预览那一路**不**调 `useNativeSize`,
|
|
13
|
+
* 否则他存好的尺寸每次重开项目都被抹掉)。
|
|
14
|
+
* - ⚠️ 排布前必须把 GLB 根节点恢复到**载入时的原始 scale/position**(`_baseScale`/`_basePos`)再量再归一:
|
|
15
|
+
* 导出器常在根上带单位换算缩放(cm→m 那类 0.01),过去无条件 `scale.setScalar(1)` 连根上的那份一起抹掉
|
|
16
|
+
* ⇒ 那就等于改了模型大小;且 `_layout()` 每次都要从同一个基准重算,否则第二次量到的是第一次缩放后的盒子。
|
|
17
|
+
* - 高亮:占位方块换色 + ⭐ 皇上旨 2026-10-22「选中设备的时候,设备还需要有一个高亮的框,参考旁边 archives2026
|
|
18
|
+
* 项目」⇒ 那圈框换成 `BoxLine2`(12 条 fat-line 棱,朝相机绿实线、被盒子挡住的红虚线;旧那圈是
|
|
19
|
+
* `LineSegments + LineBasicMaterial`,`linewidth` 在 WebGL 下是死旋钮 ⇒ 恒 1 像素,深色模型上看不见)。
|
|
20
|
+
* ⛔ 不改用户 GLB 的材质:一个 GLB 里多个 mesh 常共用同一份材质实例,
|
|
21
|
+
* 染色会顺着那份材质串到别的模型槽(同一份 glb 被放在两处时更明显 —— 两处一起变黄)。
|
|
22
|
+
* - ⚠️ 高亮框**懒建**(第一次 `highlight()` 才 new):本类编辑器与预览共用,预览那份从不选中 ⇒
|
|
23
|
+
* 一个 `BoxLine2` 是 12 个 fat-line 对象 + 一份代理盒,⛔ 不许让预览为用不上的东西按槽数付出。
|
|
24
|
+
* - `surface` 只决定**占位小立方体**的落位(floor ⇒ 底面贴原点;wall ⇒ 中心对齐原点)和存档里的落点口径。
|
|
25
|
+
* ⛔ 它**不再**决定用户上传模型的对齐 —— 皇上旨 2026-10-22「你先不过多的处理模型,让他在该有的位置」⇒
|
|
26
|
+
* 内容位置补偿整条删除,GLB 挂进来保持它自己载入时的原生 position(偏心导出的模型不再被拽到槽中心,
|
|
27
|
+
* 代价是原点不在底面的 GLB 会按原生悬空/陷地,那种情况由他在面板里改位置/旋转扳正)。
|
|
28
|
+
* - 朝向约定:模型的「正面」取 **+Z**,与 `ImagePlane` 同一条尺 ⇒ 墙面落点 `ry = atan2(n.x, n.z)`。
|
|
29
|
+
* 面板里「旋转(°)」的 Y 可以任意改,约定错了改一个数就能扳回来,不必重传模型。
|
|
30
|
+
*/
|
|
31
|
+
import { Box3, BoxGeometry, Color, Group, Mesh, MeshBasicMaterial, Vector3, } from "three";
|
|
32
|
+
import threeLoader from "../threeLoader";
|
|
33
|
+
import threeUtils from "../threeUtils";
|
|
34
|
+
import BoxLine2 from "./boxLine2";
|
|
35
|
+
/** 占位小立方体的棱长(米)—— 皇上要的是「小立方体」,比「加立方体」那颗 1m 的单位方块小一档 */
|
|
36
|
+
export const MODEL_SLOT_PLACEHOLDER = 0.3;
|
|
37
|
+
/** 默认尺寸档(米,最长边):还没上传模型时占位方块用不上它;上传成功后 `useNativeSize()` 会把它填成模型原最长边 ⇒ 一比一 */
|
|
38
|
+
export const MODEL_SLOT_DEFAULT_SIZE = 1;
|
|
39
|
+
/** 墙面落点沿法线外推的距离(米):模型中心压在墙面上会让一半陷进墙皮,且贴墙的平面片会 z-fighting */
|
|
40
|
+
export const MODEL_SLOT_WALL_OFFSET = 0.02;
|
|
41
|
+
const PLACEHOLDER_COLOR = "#c8c8c8";
|
|
42
|
+
const HIGHLIGHT_COLOR = "#ffd24a";
|
|
43
|
+
/** 高亮框套在包围盒外的余量倍率(与旧那圈细线同一档,⛔ 不是新旋钮:框压着模型表面会 z-fighting) */
|
|
44
|
+
const BOX_FIT_SCALE = 1.02;
|
|
45
|
+
/** `setSize` 的临时载体(每次贴框都要用 ⇒ 别每帧 new 一份 Vector3) */
|
|
46
|
+
const BOX_FIT_SIZE = new Vector3();
|
|
47
|
+
/** 释放一棵子树(GLB 载入失败/换模型/删除时):几何、材质、材质上的贴图各 dispose 一次 */
|
|
48
|
+
function disposeSubtree(root) {
|
|
49
|
+
const done = new Set();
|
|
50
|
+
root.traverse((o) => {
|
|
51
|
+
const mesh = o;
|
|
52
|
+
if (!mesh.isMesh)
|
|
53
|
+
return;
|
|
54
|
+
if (mesh.geometry && !done.has(mesh.geometry)) {
|
|
55
|
+
done.add(mesh.geometry);
|
|
56
|
+
mesh.geometry.dispose();
|
|
57
|
+
}
|
|
58
|
+
const mats = Array.isArray(mesh.material) ? mesh.material : mesh.material ? [mesh.material] : [];
|
|
59
|
+
for (const m of mats) {
|
|
60
|
+
if (done.has(m))
|
|
61
|
+
continue;
|
|
62
|
+
done.add(m);
|
|
63
|
+
const withMaps = m;
|
|
64
|
+
for (const key of ["map", "normalMap", "roughnessMap", "metalnessMap", "emissiveMap", "aoMap", "alphaMap", "bumpMap", "displacementMap"]) {
|
|
65
|
+
const tex = withMaps[key];
|
|
66
|
+
if (tex && typeof tex.dispose === "function" && !done.has(tex)) {
|
|
67
|
+
done.add(tex);
|
|
68
|
+
tex.dispose();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
m.dispose();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export class ModelSlot extends Group {
|
|
76
|
+
/** 拾取路由用标记(与 three 的 `isInstancedMesh` 同款判据):射线命中任意子对象后向上找带这个标记的祖先 */
|
|
77
|
+
isModelSlot = true;
|
|
78
|
+
/** 占位小立方体(未上传模型时可见) */
|
|
79
|
+
_placeholder;
|
|
80
|
+
/** GLB 挂载点:内容在这里被等比归一(10-22 旨之后位置保持 GLB 原生,⛔ 不再挪) */
|
|
81
|
+
_contentRoot;
|
|
82
|
+
/**
|
|
83
|
+
* 选中高亮框(贴合当前内容的包围盒;10-22 旨整搬 archives2026 的 `BoxLine2`)。
|
|
84
|
+
* ⚠️ 懒建 —— null = 这个槽从没被选中过(预览那一路永远是 null);⛔ 不参与射线拾取(见 `pickIgnored`)。
|
|
85
|
+
*/
|
|
86
|
+
_box = null;
|
|
87
|
+
/** 高亮框该套在哪、套多大(`_layout()` 每次算好存这里;框还没建起来时先记着,`highlight()` 建好再贴) */
|
|
88
|
+
_boxFit = null;
|
|
89
|
+
/** 当前 GLB 子树;null = 没有模型(显示占位方块) */
|
|
90
|
+
_model = null;
|
|
91
|
+
/**
|
|
92
|
+
* GLB **载入时的原姿势**(根节点自带的那份 scale/position,导出器常在根上带 cm→m 单位换算缩放)。
|
|
93
|
+
* ⚠️ 每次排布都先从这两个值复位再量再归一 ⇒ 「原尺寸」有唯一基准,且第二次 `_layout()` 不会量到
|
|
94
|
+
* 上一次 `scaleTo` 之后的盒子(那是重复缩放的源头)。`_baseFor` 记录这份基准属于哪一棵子树(换模型要重取)。
|
|
95
|
+
*/
|
|
96
|
+
_baseFor = null;
|
|
97
|
+
_baseScale = new Vector3(1, 1, 1);
|
|
98
|
+
_basePos = new Vector3(0, 0, 0);
|
|
99
|
+
_url = "";
|
|
100
|
+
_size = MODEL_SLOT_DEFAULT_SIZE;
|
|
101
|
+
_surface = "floor";
|
|
102
|
+
_naturalSize;
|
|
103
|
+
/** 载入令牌:换地址/删除后迟到的回调靠它作废(否则旧模型会挂进新槽) */
|
|
104
|
+
_loadToken = 0;
|
|
105
|
+
_disposed = false;
|
|
106
|
+
constructor(name) {
|
|
107
|
+
super();
|
|
108
|
+
this.name = name || "模型槽";
|
|
109
|
+
this.userData.kind = "modelslot";
|
|
110
|
+
this._placeholder = new Mesh(new BoxGeometry(MODEL_SLOT_PLACEHOLDER, MODEL_SLOT_PLACEHOLDER, MODEL_SLOT_PLACEHOLDER), new MeshBasicMaterial({ color: new Color(PLACEHOLDER_COLOR) }));
|
|
111
|
+
this._placeholder.name = "模型槽占位方块";
|
|
112
|
+
this.add(this._placeholder);
|
|
113
|
+
this._contentRoot = new Group();
|
|
114
|
+
this._contentRoot.name = "模型槽内容";
|
|
115
|
+
this.add(this._contentRoot);
|
|
116
|
+
this._layout();
|
|
117
|
+
}
|
|
118
|
+
get size() { return this._size; }
|
|
119
|
+
get url() { return this._url; }
|
|
120
|
+
get surface() { return this._surface; }
|
|
121
|
+
get naturalSize() { return this._naturalSize; }
|
|
122
|
+
/** 是否已经挂上真模型(false = 仍是占位小立方体) */
|
|
123
|
+
get hasModel() { return this._model !== null; }
|
|
124
|
+
/** 改尺寸档(米,最长边):立刻重缩内容 */
|
|
125
|
+
setSize(size) {
|
|
126
|
+
this._size = size > 0 ? size : this._size;
|
|
127
|
+
this._layout();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 把尺寸档填成**模型自己的原最长边** ⇒ 之后那步归一算出的倍率正好是 1 ⇒ 画面上一比一,⛔ 不改模型大小。
|
|
131
|
+
* ⭐ 皇上旨 2026-10-21「上传模型的时候,不要去修改模型的大小」:只给**上传**那一路调(渲染器 `setModelGlb`);
|
|
132
|
+
* 存档导入 / 预览重建那一路**不能**调,否则他在面板里存的尺寸每次重开项目都被抹回原大小。
|
|
133
|
+
* @returns 是否真的改动了(还没挂上模型 ⇒ false,尺寸档保持原样)
|
|
134
|
+
*/
|
|
135
|
+
useNativeSize() {
|
|
136
|
+
const n = this._naturalSize;
|
|
137
|
+
if (!this._model || !n)
|
|
138
|
+
return false;
|
|
139
|
+
const maxEdge = Math.max(n.x, n.y, n.z);
|
|
140
|
+
if (!(maxEdge > 0))
|
|
141
|
+
return false;
|
|
142
|
+
this._size = maxEdge;
|
|
143
|
+
this._layout();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
/** 改落点面(地面/墙面):重摆占位方块的落位 + 重算高亮框(用户上传的模型位置不动,见 10-22 旨) */
|
|
147
|
+
setSurface(surface) {
|
|
148
|
+
this._surface = surface;
|
|
149
|
+
this._layout();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 载入并挂上 GLB(`url` 必须是可直接 fetch 的绝对地址;空串 = 退回占位小立方体)。
|
|
153
|
+
* @returns 是否真的挂上了(地址无效/载入失败/这次结果已作废 ⇒ false)
|
|
154
|
+
*/
|
|
155
|
+
async setModelUrl(url) {
|
|
156
|
+
this._url = url;
|
|
157
|
+
const token = ++this._loadToken;
|
|
158
|
+
this._detachModel();
|
|
159
|
+
// 先立刻退回占位小立方体:GLB 在路上那段窗口(慢网/大模型)⛔ 不许让这一槽凭空消失
|
|
160
|
+
// (取回失败那条支路本来也是停在占位上,两段时间里画面一致)
|
|
161
|
+
this._layout();
|
|
162
|
+
if (!url)
|
|
163
|
+
return true;
|
|
164
|
+
const obj = await threeLoader.loadModel(url);
|
|
165
|
+
// 迟到回调:这一槽已经改指向别的地址、或已经删除 ⇒ 当场释放,别留一份没人认领的显存
|
|
166
|
+
if (this._disposed || token !== this._loadToken) {
|
|
167
|
+
if (obj)
|
|
168
|
+
disposeSubtree(obj);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (!obj) {
|
|
172
|
+
this._layout();
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
this._model = obj;
|
|
176
|
+
this._contentRoot.add(obj);
|
|
177
|
+
this._layout();
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 把存档里的数据写进来(位置 / 旋转 / 尺寸 / 落点面 / 地址)。
|
|
182
|
+
* ⚠️ 刻意**不**在这里发起 GLB 载入:载入是异步的,落地那一刻必须由调用方(编辑器导入 / 预览)
|
|
183
|
+
* 显式标脏才能画出来 —— 换成 `modelRoot` 的子对象不改任何材质版本号。
|
|
184
|
+
* 故本方法只把 `url` 记好,取模型由调用方 `setModelUrl(d.url).then(...)` 驱动(⛔ 别在这儿偷偷 void)。
|
|
185
|
+
*/
|
|
186
|
+
applyData(d) {
|
|
187
|
+
if (d.name)
|
|
188
|
+
this.name = d.name;
|
|
189
|
+
this.position.set(d.position.x, d.position.y, d.position.z);
|
|
190
|
+
this.rotation.set(d.rotation.x, d.rotation.y, d.rotation.z);
|
|
191
|
+
this._surface = d.surface === "wall" ? "wall" : "floor";
|
|
192
|
+
this._size = d.size > 0 ? d.size : MODEL_SLOT_DEFAULT_SIZE;
|
|
193
|
+
this._naturalSize = d.naturalSize;
|
|
194
|
+
this._url = d.url || "";
|
|
195
|
+
if (!this._url)
|
|
196
|
+
this._detachModel();
|
|
197
|
+
this._layout();
|
|
198
|
+
}
|
|
199
|
+
toData() {
|
|
200
|
+
return {
|
|
201
|
+
name: this.name || undefined,
|
|
202
|
+
position: { x: this.position.x, y: this.position.y, z: this.position.z },
|
|
203
|
+
rotation: { x: this.rotation.x, y: this.rotation.y, z: this.rotation.z },
|
|
204
|
+
size: this._size,
|
|
205
|
+
url: this._url,
|
|
206
|
+
surface: this._surface,
|
|
207
|
+
naturalSize: this._naturalSize,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
highlight() {
|
|
211
|
+
this._placeholder.material.color.set(HIGHLIGHT_COLOR);
|
|
212
|
+
this._ensureBox().visible = true;
|
|
213
|
+
}
|
|
214
|
+
restoreColor() {
|
|
215
|
+
this._placeholder.material.color.set(PLACEHOLDER_COLOR);
|
|
216
|
+
if (this._box)
|
|
217
|
+
this._box.visible = false;
|
|
218
|
+
}
|
|
219
|
+
dispose() {
|
|
220
|
+
this._disposed = true;
|
|
221
|
+
this._loadToken++;
|
|
222
|
+
this._detachModel();
|
|
223
|
+
this._placeholder.geometry.dispose();
|
|
224
|
+
this._placeholder.material.dispose();
|
|
225
|
+
if (this._box) {
|
|
226
|
+
this._box.dispose();
|
|
227
|
+
this.remove(this._box);
|
|
228
|
+
this._box = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 取出(并第一次需要时建起)高亮框:挂进槽、贴上最近一次算好的包围盒。
|
|
233
|
+
* ⚠️ 建好即 `visible = false` —— 调用方拿到对象后自己决定亮不亮(`highlight()` 里点亮)。
|
|
234
|
+
*/
|
|
235
|
+
_ensureBox() {
|
|
236
|
+
if (!this._box) {
|
|
237
|
+
const box = new BoxLine2();
|
|
238
|
+
box.name = `${this.name || "模型槽"}-高亮框`;
|
|
239
|
+
box.visible = false;
|
|
240
|
+
this._box = box;
|
|
241
|
+
this.add(box);
|
|
242
|
+
}
|
|
243
|
+
this._applyBoxFit();
|
|
244
|
+
return this._box;
|
|
245
|
+
}
|
|
246
|
+
/** 把 `_boxFit` 写进框(尺寸 = 包围盒外扩 `BOX_FIT_SCALE`,中心 = 本槽局部系下的盒心) */
|
|
247
|
+
_applyBoxFit() {
|
|
248
|
+
if (!this._box)
|
|
249
|
+
return;
|
|
250
|
+
if (!this._boxFit) {
|
|
251
|
+
this._box.visible = false;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const s = this._boxFit.size;
|
|
255
|
+
// 零厚度那一路钳到 1e-4(薄一片的 GLB 会量出某个轴 = 0,⛔ 不许把线段端点重合到一处)
|
|
256
|
+
BOX_FIT_SIZE.set(Math.max(s.x, 1e-4), Math.max(s.y, 1e-4), Math.max(s.z, 1e-4)).multiplyScalar(BOX_FIT_SCALE);
|
|
257
|
+
this._box.setSize(BOX_FIT_SIZE);
|
|
258
|
+
this._box.position.copy(this._boxFit.center);
|
|
259
|
+
}
|
|
260
|
+
/** 摘掉 GLB(几何/材质/贴图一起释放,否则换一只模型就漏一份显存) */
|
|
261
|
+
_detachModel() {
|
|
262
|
+
if (!this._model)
|
|
263
|
+
return;
|
|
264
|
+
this._contentRoot.remove(this._model);
|
|
265
|
+
disposeSubtree(this._model);
|
|
266
|
+
this._model = null;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* 内容排布的唯一出口:GLB 等比归一 + 占位方块显隐 + 高亮框贴合(⛔ 不再挪模型的位置,见 `_layoutNeutral`)。
|
|
270
|
+
* ⚠️ 每次改 size / surface / 模型都要走这里(⛔ 别在别处直接写 `_contentRoot.position`)。
|
|
271
|
+
* ⚠️ 皇上旨 2026-10-21「模型的位置和对应的边框不在一起」:这段曾把 `Box3.setFromObject` 量到的
|
|
272
|
+
* **世界**盒子直接写进子对象的**局部** `position`(内容补偿与框中心都是),于是槽离原点越远偏得越远
|
|
273
|
+
* —— 框比模型多偏一整份槽的位置。归一与套框两件事都只该**相对本槽**算,故量之前先把槽自身的位移与
|
|
274
|
+
* 旋转摘掉,算完原样装回(10-22 旨删掉内容补偿后,这把「摘了再装回」的尺仍要为高亮框留着)。
|
|
275
|
+
*/
|
|
276
|
+
_layout() {
|
|
277
|
+
const savedPos = this.position.clone();
|
|
278
|
+
// ⚠️ 存取都走 `rotation`(欧拉),⛔ 别拿 `quaternion` 存再 copy 回来:四元数↔欧拉那趟换算会把
|
|
279
|
+
// 存档里的角度数字漂掉(1.5708 变 1.57079265…,护栏 BV ⑫ 的落盘逐字闭环当场红)。
|
|
280
|
+
const savedRot = this.rotation.clone();
|
|
281
|
+
this.position.set(0, 0, 0);
|
|
282
|
+
this.rotation.set(0, 0, 0);
|
|
283
|
+
this.updateMatrixWorld(true);
|
|
284
|
+
try {
|
|
285
|
+
this._layoutNeutral();
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
this.position.copy(savedPos);
|
|
289
|
+
this.rotation.copy(savedRot);
|
|
290
|
+
this.updateMatrixWorld(true);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** 只在「本槽自身位移/旋转已摘除」的世界帧里跑 ⇒ 下面量的每个盒子都等于相对本槽的盒子 */
|
|
294
|
+
_layoutNeutral() {
|
|
295
|
+
const m = this._model;
|
|
296
|
+
if (m) {
|
|
297
|
+
// 复位到 GLB 载入时的原姿势(根上带单位换算缩放的那种导出器,⛔ 不能顺手 setScalar(1) 抹掉它)
|
|
298
|
+
if (this._baseFor !== m) {
|
|
299
|
+
this._baseFor = m;
|
|
300
|
+
this._baseScale.copy(m.scale);
|
|
301
|
+
this._basePos.copy(m.position);
|
|
302
|
+
}
|
|
303
|
+
m.scale.copy(this._baseScale);
|
|
304
|
+
m.position.copy(this._basePos);
|
|
305
|
+
m.updateMatrixWorld(true);
|
|
306
|
+
const box = new Box3().setFromObject(m);
|
|
307
|
+
if (!box.isEmpty()) {
|
|
308
|
+
const s = box.getSize(new Vector3());
|
|
309
|
+
this._naturalSize = { x: s.x, y: s.y, z: s.z };
|
|
310
|
+
}
|
|
311
|
+
// 等比缩到尺寸档 = 原最长边时(上传那一路,`useNativeSize()` 刚把 `_size` 填成它)倍率正好是 1
|
|
312
|
+
// ⇒ 模型保持原大小;他在面板里把那一栏改成别的数才真的缩放(⛔ 不是逐轴拉满)
|
|
313
|
+
threeUtils.scaleTo(m, this._size);
|
|
314
|
+
m.updateMatrixWorld(true);
|
|
315
|
+
// ⛔ 这里过去还会写一份「位置补偿」(皇上旨 2026-10-22「你先不过多的处理模型,让他在该有的位置」⇒ 删掉):
|
|
316
|
+
// 旧那一刀量出内容包围盒后写 `m.position.set(-cx, -cy, -cz)`,把盒中心拽到槽原点(地面还把**底面**贴 y=0、
|
|
317
|
+
// 墙面三轴居中)。导出时偏心摆放的 GLB(物体在原点旁几十米)或原点不在底面的模型,一进来就被整只挪走。
|
|
318
|
+
// 现在 `m.position` 逐字保持上面 `_basePos` 复位回去的那份 = **GLB 载入时的原生位置**,一行都不改。
|
|
319
|
+
}
|
|
320
|
+
this._placeholder.visible = m === null;
|
|
321
|
+
const ph = MODEL_SLOT_PLACEHOLDER;
|
|
322
|
+
this._placeholder.position.set(0, this._surface === "wall" ? 0 : ph / 2, 0);
|
|
323
|
+
// 选中框贴合当前内容(外面套 `BOX_FIT_SCALE` 倍免得线与模型表面共面闪)
|
|
324
|
+
// ⚠️ 先更矩阵再量包围盒:上面刚 `scaleTo` 改过根上的 scale,`setFromObject` 读的是 matrixWorld,
|
|
325
|
+
// 不先更新就量到「缩放之前」的盒子 ⇒ 选中框整只偏离模型(皇上旨 10-21 那条血案的同一把尺)。
|
|
326
|
+
this.updateMatrixWorld(true);
|
|
327
|
+
const b = new Box3().setFromObject(m ? m : this._placeholder);
|
|
328
|
+
if (b.isEmpty()) {
|
|
329
|
+
this._boxFit = null;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
if (!this._boxFit)
|
|
333
|
+
this._boxFit = { center: new Vector3(), size: new Vector3() };
|
|
334
|
+
b.getCenter(this._boxFit.center);
|
|
335
|
+
b.getSize(this._boxFit.size);
|
|
336
|
+
}
|
|
337
|
+
this._applyBoxFit();
|
|
338
|
+
}
|
|
339
|
+
}
|