@qy_better_lib/hooks 0.2.9 → 0.2.11
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/DOCUMENTATION.md +104 -8
- package/lib/use-three/index.d.ts +8 -0
- package/lib/use-three/index.mjs +1233 -0
- package/lib/use-three/interface.d.ts +204 -0
- package/lib/use-three/modules/box-helper.d.ts +15 -0
- package/lib/use-three/modules/camera.d.ts +15 -0
- package/lib/use-three/modules/effect-composer.d.ts +45 -0
- package/lib/use-three/modules/interaction.d.ts +21 -0
- package/lib/use-three/modules/lighting.d.ts +15 -0
- package/lib/use-three/modules/loader.d.ts +28 -0
- package/lib/use-three/modules/renderer.d.ts +18 -0
- package/lib/use-three/modules/resources.d.ts +12 -0
- package/lib/use-three/modules/utils.d.ts +133 -0
- package/lib/use-three/toolTip.d.ts +101 -0
- package/lib/use-three/type.d.ts +82 -0
- package/lib/use-three/utils.d.ts +11 -0
- package/package.json +11 -1
|
@@ -0,0 +1,1233 @@
|
|
|
1
|
+
import { onMounted, onBeforeUnmount } from "vue";
|
|
2
|
+
import { Box3, Vector3, AxesHelper, OrthographicCamera, PerspectiveCamera, PCFSoftShadowMap, SpotLight, PointLight, DirectionalLight, AmbientLight, WebGLRenderer, Vector2, Raycaster, Cache, BoxHelper, Object3D, Scene, Color } from "three";
|
|
3
|
+
import { VRButton } from "three/examples/jsm/webxr/VRButton.js";
|
|
4
|
+
import { CSS2DObject, CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";
|
|
5
|
+
import { on, off } from "@qy_better_lib/core";
|
|
6
|
+
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
7
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
8
|
+
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader.js";
|
|
9
|
+
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
10
|
+
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
|
|
11
|
+
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
12
|
+
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
|
|
13
|
+
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
|
|
14
|
+
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
|
|
15
|
+
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
|
|
16
|
+
import { SSAOPass } from "three/examples/jsm/postprocessing/SSAOPass.js";
|
|
17
|
+
import { FilmPass } from "three/examples/jsm/postprocessing/FilmPass.js";
|
|
18
|
+
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
|
|
19
|
+
import { GrayscaleShader } from "three/examples/jsm/shaders/GrayscaleShader.js";
|
|
20
|
+
class ObjectToolTip {
|
|
21
|
+
/**
|
|
22
|
+
* 关联的3D对象
|
|
23
|
+
*/
|
|
24
|
+
obj = void 0;
|
|
25
|
+
/**
|
|
26
|
+
* 工具提示元素
|
|
27
|
+
*/
|
|
28
|
+
container = null;
|
|
29
|
+
/**
|
|
30
|
+
* CSS2D对象,用于在3D场景中渲染HTML元素
|
|
31
|
+
*/
|
|
32
|
+
css2Object;
|
|
33
|
+
/**
|
|
34
|
+
* 事件回调
|
|
35
|
+
*/
|
|
36
|
+
events;
|
|
37
|
+
/**
|
|
38
|
+
* 是否显示
|
|
39
|
+
*/
|
|
40
|
+
isVisible = false;
|
|
41
|
+
/**
|
|
42
|
+
* 提示框内容
|
|
43
|
+
*/
|
|
44
|
+
content = "";
|
|
45
|
+
/**
|
|
46
|
+
* 创建3D对象工具提示
|
|
47
|
+
* @param obj 3D对象
|
|
48
|
+
* @param options 配置选项
|
|
49
|
+
*/
|
|
50
|
+
create(obj, options) {
|
|
51
|
+
this.obj = obj;
|
|
52
|
+
this.events = {
|
|
53
|
+
onClose: options?.onClose
|
|
54
|
+
};
|
|
55
|
+
if (this.isVisible) return;
|
|
56
|
+
const id = this.obj.uuid;
|
|
57
|
+
this.container = document.createElement("div");
|
|
58
|
+
this.container.id = id;
|
|
59
|
+
this.container.className = `object-3d-tip ${options?.className || ""}`.trim();
|
|
60
|
+
const closeBtn = document.createElement("div");
|
|
61
|
+
closeBtn.className = "object-3d-tip-close";
|
|
62
|
+
closeBtn.innerHTML = `
|
|
63
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
64
|
+
<path d="M10.5858 12L6.34315 16.2426L7.75736 17.6568L12 13.4142L16.2426 17.6568L17.6569 16.2426L13.4142 12L17.6569 7.75732L16.2426 6.3431L12 10.5857L7.75736 6.3431L6.34315 7.75732L10.5858 12Z" fill-rule="evenodd" fill="currentColor">
|
|
65
|
+
</path>
|
|
66
|
+
</svg>`;
|
|
67
|
+
closeBtn.addEventListener("click", () => {
|
|
68
|
+
this.remove();
|
|
69
|
+
this.events?.onClose?.();
|
|
70
|
+
});
|
|
71
|
+
if (options?.content) {
|
|
72
|
+
this.content = options.content;
|
|
73
|
+
const contentElement = document.createElement("div");
|
|
74
|
+
contentElement.className = "object-3d-tip-content";
|
|
75
|
+
contentElement.innerHTML = this.content;
|
|
76
|
+
this.container.appendChild(contentElement);
|
|
77
|
+
}
|
|
78
|
+
this.container.appendChild(closeBtn);
|
|
79
|
+
this.css2Object = new CSS2DObject(this.container);
|
|
80
|
+
const box3 = new Box3().setFromObject(this.obj);
|
|
81
|
+
const boxSize = box3.getSize(new Vector3());
|
|
82
|
+
const position = new Vector3(
|
|
83
|
+
options?.offset?.x || 0,
|
|
84
|
+
options?.offset?.y || boxSize.y / this.obj.scale.y + 5 / this.obj.scale.y,
|
|
85
|
+
options?.offset?.z || 0
|
|
86
|
+
);
|
|
87
|
+
this.css2Object.position.copy(position);
|
|
88
|
+
this.obj.add(this.css2Object);
|
|
89
|
+
this.isVisible = true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 更新提示框内容
|
|
93
|
+
* @param content 新的内容
|
|
94
|
+
*/
|
|
95
|
+
updateContent(content) {
|
|
96
|
+
if (!this.container) return;
|
|
97
|
+
this.content = content;
|
|
98
|
+
let contentElement = this.container.querySelector(".object-3d-tip-content");
|
|
99
|
+
if (!contentElement) {
|
|
100
|
+
contentElement = document.createElement("div");
|
|
101
|
+
contentElement.className = "object-3d-tip-content";
|
|
102
|
+
this.container.insertBefore(contentElement, this.container.firstChild);
|
|
103
|
+
}
|
|
104
|
+
contentElement.innerHTML = content;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 更新提示框位置
|
|
108
|
+
* @param position 新的位置
|
|
109
|
+
*/
|
|
110
|
+
updatePosition(position) {
|
|
111
|
+
if (!this.css2Object) return;
|
|
112
|
+
this.css2Object.position.copy(position);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 显示提示框
|
|
116
|
+
*/
|
|
117
|
+
show() {
|
|
118
|
+
if (!this.container) return;
|
|
119
|
+
this.container.style.display = "block";
|
|
120
|
+
this.isVisible = true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 隐藏提示框
|
|
124
|
+
*/
|
|
125
|
+
hide() {
|
|
126
|
+
if (!this.container) return;
|
|
127
|
+
this.container.style.display = "none";
|
|
128
|
+
this.isVisible = false;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 移除提示框
|
|
132
|
+
*/
|
|
133
|
+
remove() {
|
|
134
|
+
this.clear();
|
|
135
|
+
this.obj = void 0;
|
|
136
|
+
this.isVisible = false;
|
|
137
|
+
this.css2Object = void 0;
|
|
138
|
+
this.events = void 0;
|
|
139
|
+
this.content = "";
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 清理资源
|
|
143
|
+
*/
|
|
144
|
+
clear() {
|
|
145
|
+
if (this.obj && this.css2Object) {
|
|
146
|
+
this.obj.remove(this.css2Object);
|
|
147
|
+
}
|
|
148
|
+
if (this.container && this.container.parentNode) {
|
|
149
|
+
this.container.parentNode.removeChild(this.container);
|
|
150
|
+
this.container = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 检查提示框是否可见
|
|
155
|
+
* @returns 是否可见
|
|
156
|
+
*/
|
|
157
|
+
isShowing() {
|
|
158
|
+
return this.isVisible;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 获取提示框内容
|
|
162
|
+
* @returns 提示框内容
|
|
163
|
+
*/
|
|
164
|
+
getContent() {
|
|
165
|
+
return this.content;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function safe_execute(fn, default_value) {
|
|
169
|
+
try {
|
|
170
|
+
return fn();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error("Three.js error:", error);
|
|
173
|
+
return default_value;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const three_utils = {
|
|
177
|
+
/**
|
|
178
|
+
* 计算对象边界盒
|
|
179
|
+
* @param object 3D对象
|
|
180
|
+
* @returns 边界盒对象
|
|
181
|
+
*/
|
|
182
|
+
compute_bounding_box: (object) => {
|
|
183
|
+
return safe_execute(() => {
|
|
184
|
+
const box = new Box3().setFromObject(object);
|
|
185
|
+
return box;
|
|
186
|
+
}, null);
|
|
187
|
+
},
|
|
188
|
+
/**
|
|
189
|
+
* 计算对象中心点
|
|
190
|
+
* @param object 3D对象
|
|
191
|
+
* @returns 中心点坐标
|
|
192
|
+
*/
|
|
193
|
+
compute_center: (object) => {
|
|
194
|
+
return safe_execute(() => {
|
|
195
|
+
const box = new Box3().setFromObject(object);
|
|
196
|
+
const center = new Vector3();
|
|
197
|
+
box.getCenter(center);
|
|
198
|
+
return center;
|
|
199
|
+
}, new Vector3());
|
|
200
|
+
},
|
|
201
|
+
/**
|
|
202
|
+
* 生成网格辅助线
|
|
203
|
+
* @param size 网格大小
|
|
204
|
+
* @param divisions 网格细分
|
|
205
|
+
* @returns 网格辅助线对象
|
|
206
|
+
*/
|
|
207
|
+
create_grid_helper: (size = 100, divisions = 10) => {
|
|
208
|
+
return safe_execute(() => {
|
|
209
|
+
const { GridHelper } = require("three");
|
|
210
|
+
const grid_helper = new GridHelper(size, divisions);
|
|
211
|
+
return grid_helper;
|
|
212
|
+
}, null);
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* 创建坐标轴辅助器
|
|
216
|
+
* @param size 坐标轴大小
|
|
217
|
+
* @returns 坐标轴辅助器对象
|
|
218
|
+
*/
|
|
219
|
+
create_axes_helper: (size = 50) => {
|
|
220
|
+
return safe_execute(() => {
|
|
221
|
+
const { AxesHelper: AxesHelper2 } = require("three");
|
|
222
|
+
const axes_helper = new AxesHelper2(size);
|
|
223
|
+
return axes_helper;
|
|
224
|
+
}, null);
|
|
225
|
+
},
|
|
226
|
+
/**
|
|
227
|
+
* 创建立方体
|
|
228
|
+
* @param size 立方体大小
|
|
229
|
+
* @param material 材质
|
|
230
|
+
* @returns 立方体对象
|
|
231
|
+
*/
|
|
232
|
+
create_box: (size = 1, material) => {
|
|
233
|
+
return safe_execute(() => {
|
|
234
|
+
const { BoxGeometry, Mesh, MeshStandardMaterial } = require("three");
|
|
235
|
+
const geometry = new BoxGeometry(size, size, size);
|
|
236
|
+
const mat = material || new MeshStandardMaterial({ color: 65280 });
|
|
237
|
+
const box = new Mesh(geometry, mat);
|
|
238
|
+
return box;
|
|
239
|
+
}, null);
|
|
240
|
+
},
|
|
241
|
+
/**
|
|
242
|
+
* 创建球体
|
|
243
|
+
* @param radius 球体半径
|
|
244
|
+
* @param material 材质
|
|
245
|
+
* @returns 球体对象
|
|
246
|
+
*/
|
|
247
|
+
create_sphere: (radius = 1, material) => {
|
|
248
|
+
return safe_execute(() => {
|
|
249
|
+
const { SphereGeometry, Mesh, MeshStandardMaterial } = require("three");
|
|
250
|
+
const geometry = new SphereGeometry(radius, 32, 32);
|
|
251
|
+
const mat = material || new MeshStandardMaterial({ color: 16711680 });
|
|
252
|
+
const sphere = new Mesh(geometry, mat);
|
|
253
|
+
return sphere;
|
|
254
|
+
}, null);
|
|
255
|
+
},
|
|
256
|
+
/**
|
|
257
|
+
* 创建圆柱体
|
|
258
|
+
* @param radius_top 顶部半径
|
|
259
|
+
* @param radius_bottom 底部半径
|
|
260
|
+
* @param height 高度
|
|
261
|
+
* @param material 材质
|
|
262
|
+
* @returns 圆柱体对象
|
|
263
|
+
*/
|
|
264
|
+
create_cylinder: (radius_top = 1, radius_bottom = 1, height = 2, material) => {
|
|
265
|
+
return safe_execute(() => {
|
|
266
|
+
const { CylinderGeometry, Mesh, MeshStandardMaterial } = require("three");
|
|
267
|
+
const geometry = new CylinderGeometry(radius_top, radius_bottom, height, 32);
|
|
268
|
+
const mat = material || new MeshStandardMaterial({ color: 255 });
|
|
269
|
+
const cylinder = new Mesh(geometry, mat);
|
|
270
|
+
return cylinder;
|
|
271
|
+
}, null);
|
|
272
|
+
},
|
|
273
|
+
/**
|
|
274
|
+
* 屏幕坐标转世界坐标
|
|
275
|
+
* @param x 屏幕X坐标
|
|
276
|
+
* @param y 屏幕Y坐标
|
|
277
|
+
* @param camera 相机
|
|
278
|
+
* @returns 世界坐标
|
|
279
|
+
*/
|
|
280
|
+
screen_to_world: (x, y, camera) => {
|
|
281
|
+
return safe_execute(() => {
|
|
282
|
+
const { Vector3: Vector32 } = require("three");
|
|
283
|
+
const vector = new Vector32();
|
|
284
|
+
vector.set(
|
|
285
|
+
x / window.innerWidth * 2 - 1,
|
|
286
|
+
-(y / window.innerHeight) * 2 + 1,
|
|
287
|
+
0.5
|
|
288
|
+
);
|
|
289
|
+
vector.unproject(camera);
|
|
290
|
+
const dir = vector.sub(camera.position).normalize();
|
|
291
|
+
const distance = -camera.position.z / dir.z;
|
|
292
|
+
const pos = camera.position.clone().add(dir.multiplyScalar(distance));
|
|
293
|
+
return pos;
|
|
294
|
+
}, new Vector3());
|
|
295
|
+
},
|
|
296
|
+
/**
|
|
297
|
+
* 世界坐标转屏幕坐标
|
|
298
|
+
* @param position 世界坐标
|
|
299
|
+
* @param camera 相机
|
|
300
|
+
* @returns 屏幕坐标
|
|
301
|
+
*/
|
|
302
|
+
world_to_screen: (position, camera) => {
|
|
303
|
+
return safe_execute(() => {
|
|
304
|
+
const vector = position.clone();
|
|
305
|
+
vector.project(camera);
|
|
306
|
+
const x = (vector.x + 1) / 2 * window.innerWidth;
|
|
307
|
+
const y = -(vector.y - 1) / 2 * window.innerHeight;
|
|
308
|
+
return { x, y };
|
|
309
|
+
}, { x: 0, y: 0 });
|
|
310
|
+
},
|
|
311
|
+
/**
|
|
312
|
+
* 平滑动画函数
|
|
313
|
+
* @param start 起始值
|
|
314
|
+
* @param end 结束值
|
|
315
|
+
* @param duration 动画时长
|
|
316
|
+
* @param callback 每帧回调
|
|
317
|
+
* @param ease 缓动函数
|
|
318
|
+
*/
|
|
319
|
+
animate: (start, end, duration, callback, ease) => {
|
|
320
|
+
const start_time = performance.now();
|
|
321
|
+
const ease_fn = ease || ((t) => t);
|
|
322
|
+
function animate_frame(current_time) {
|
|
323
|
+
const elapsed = current_time - start_time;
|
|
324
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
325
|
+
const eased_progress = ease_fn(progress);
|
|
326
|
+
const value = start + (end - start) * eased_progress;
|
|
327
|
+
callback(value);
|
|
328
|
+
if (progress < 1) {
|
|
329
|
+
requestAnimationFrame(animate_frame);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
requestAnimationFrame(animate_frame);
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* 缓动函数集合
|
|
336
|
+
*/
|
|
337
|
+
easing: {
|
|
338
|
+
linear: (t) => t,
|
|
339
|
+
ease_in_quad: (t) => t * t,
|
|
340
|
+
ease_out_quad: (t) => t * (2 - t),
|
|
341
|
+
ease_in_out_quad: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
342
|
+
ease_in_cubic: (t) => t * t * t,
|
|
343
|
+
ease_out_cubic: (t) => --t * t * t + 1,
|
|
344
|
+
ease_in_out_cubic: (t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
const camera_presets = {
|
|
348
|
+
front: { x: 50, y: 0, z: 0 },
|
|
349
|
+
back: { x: -50, y: 0, z: 0 },
|
|
350
|
+
top: { x: 0, y: 50, z: 0 },
|
|
351
|
+
bottom: { x: 0, y: -50, z: 0 },
|
|
352
|
+
left: { x: 0, y: 0, z: 50 },
|
|
353
|
+
right: { x: 0, y: 0, z: -50 }
|
|
354
|
+
};
|
|
355
|
+
function use_camera(current_three) {
|
|
356
|
+
function create_perspective_camera(options) {
|
|
357
|
+
current_three.camera && current_three.scene?.remove(current_three.camera);
|
|
358
|
+
current_three.camera = new PerspectiveCamera(
|
|
359
|
+
options.camera_option?.fov,
|
|
360
|
+
(current_three.container?.clientWidth || window.innerWidth) / (current_three.container?.clientHeight || window.innerHeight),
|
|
361
|
+
options.camera_option?.near,
|
|
362
|
+
options.camera_option?.far
|
|
363
|
+
);
|
|
364
|
+
const { x, y, z } = options.camera_option.position;
|
|
365
|
+
current_three.camera.position.set(x, y, z);
|
|
366
|
+
current_three.scene?.add(current_three.camera);
|
|
367
|
+
}
|
|
368
|
+
function create_orthographic_camera(options) {
|
|
369
|
+
const width = current_three.container?.clientWidth || window.innerWidth;
|
|
370
|
+
const height = current_three.container?.clientHeight || window.innerHeight;
|
|
371
|
+
const orthoOptions = options || {
|
|
372
|
+
left: -width / 2,
|
|
373
|
+
right: width / 2,
|
|
374
|
+
top: height / 2,
|
|
375
|
+
bottom: -height / 2,
|
|
376
|
+
near: 0.1,
|
|
377
|
+
far: 800,
|
|
378
|
+
position: { x: 0, y: 0, z: 50 }
|
|
379
|
+
};
|
|
380
|
+
current_three.camera && current_three.scene?.remove(current_three.camera);
|
|
381
|
+
current_three.camera = new OrthographicCamera(
|
|
382
|
+
orthoOptions.left,
|
|
383
|
+
orthoOptions.right,
|
|
384
|
+
orthoOptions.top,
|
|
385
|
+
orthoOptions.bottom,
|
|
386
|
+
orthoOptions.near,
|
|
387
|
+
orthoOptions.far
|
|
388
|
+
);
|
|
389
|
+
const { x, y, z } = orthoOptions.position;
|
|
390
|
+
current_three.camera.position.set(x, y, z);
|
|
391
|
+
current_three.scene?.add(current_three.camera);
|
|
392
|
+
}
|
|
393
|
+
function set_camera_preset(preset) {
|
|
394
|
+
const position = camera_presets[preset];
|
|
395
|
+
if (position) {
|
|
396
|
+
current_three.camera?.position.set(position.x, position.y, position.z);
|
|
397
|
+
current_three.camera?.lookAt(0, 0, 0);
|
|
398
|
+
current_three.controls?.update();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function orbit_controls() {
|
|
402
|
+
current_three.controls = new OrbitControls(
|
|
403
|
+
current_three.camera,
|
|
404
|
+
current_three.renderer?.domElement
|
|
405
|
+
);
|
|
406
|
+
current_three.controls.enableDamping = true;
|
|
407
|
+
current_three.controls.saveState();
|
|
408
|
+
}
|
|
409
|
+
function axes_helper(size = 50) {
|
|
410
|
+
current_three.axesHelper = new AxesHelper(size);
|
|
411
|
+
current_three.scene?.add(current_three.axesHelper);
|
|
412
|
+
}
|
|
413
|
+
function reset_camera(options) {
|
|
414
|
+
const { x, y, z } = options.camera_option.position;
|
|
415
|
+
current_three.camera?.position.set(x, y, z);
|
|
416
|
+
current_three.controls?.reset();
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
create_perspective_camera,
|
|
420
|
+
create_orthographic_camera,
|
|
421
|
+
set_camera_preset,
|
|
422
|
+
orbit_controls,
|
|
423
|
+
axes_helper,
|
|
424
|
+
reset_camera
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function use_lighting(current_three) {
|
|
428
|
+
function add_default_lighting() {
|
|
429
|
+
add_ambient_light({ color: 16777215, intensity: 0.5 });
|
|
430
|
+
add_directional_light({
|
|
431
|
+
color: 16777215,
|
|
432
|
+
intensity: 0.8,
|
|
433
|
+
position: { x: 10, y: 10, z: 5 }
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function add_ambient_light(options) {
|
|
437
|
+
const { color = 16777215, intensity = 0.5 } = options || {};
|
|
438
|
+
const ambientLight = new AmbientLight(color, intensity);
|
|
439
|
+
current_three.scene?.add(ambientLight);
|
|
440
|
+
return ambientLight;
|
|
441
|
+
}
|
|
442
|
+
function add_directional_light(options) {
|
|
443
|
+
const { color = 16777215, intensity = 1, position = { x: 0, y: 1, z: 0 } } = options || {};
|
|
444
|
+
const directionalLight = new DirectionalLight(color, intensity);
|
|
445
|
+
directionalLight.position.set(position.x, position.y, position.z);
|
|
446
|
+
current_three.scene?.add(directionalLight);
|
|
447
|
+
return directionalLight;
|
|
448
|
+
}
|
|
449
|
+
function add_point_light(options) {
|
|
450
|
+
const { color = 16777215, intensity = 1, position = { x: 0, y: 0, z: 0 } } = options || {};
|
|
451
|
+
const pointLight = new PointLight(color, intensity);
|
|
452
|
+
pointLight.position.set(position.x, position.y, position.z);
|
|
453
|
+
current_three.scene?.add(pointLight);
|
|
454
|
+
return pointLight;
|
|
455
|
+
}
|
|
456
|
+
function add_spot_light(options) {
|
|
457
|
+
const { color = 16777215, intensity = 1, position = { x: 0, y: 10, z: 0 } } = options || {};
|
|
458
|
+
const spotLight = new SpotLight(color, intensity);
|
|
459
|
+
spotLight.position.set(position.x, position.y, position.z);
|
|
460
|
+
spotLight.target.position.set(0, 0, 0);
|
|
461
|
+
current_three.scene?.add(spotLight);
|
|
462
|
+
current_three.scene?.add(spotLight.target);
|
|
463
|
+
return spotLight;
|
|
464
|
+
}
|
|
465
|
+
function enable_shadow() {
|
|
466
|
+
current_three.renderer.shadowMap.enabled = true;
|
|
467
|
+
current_three.renderer.shadowMap.type = PCFSoftShadowMap;
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
add_default_lighting,
|
|
471
|
+
add_ambient_light,
|
|
472
|
+
add_directional_light,
|
|
473
|
+
add_point_light,
|
|
474
|
+
add_spot_light,
|
|
475
|
+
enable_shadow
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function use_renderer(current_three) {
|
|
479
|
+
let is_rendering = true;
|
|
480
|
+
let last_render_time = 0;
|
|
481
|
+
let target_fps = 60;
|
|
482
|
+
let frame_interval = 1e3 / target_fps;
|
|
483
|
+
let animation_frame_id;
|
|
484
|
+
function init_renderer(options) {
|
|
485
|
+
current_three.renderer = new WebGLRenderer({
|
|
486
|
+
// 抗锯齿
|
|
487
|
+
antialias: true,
|
|
488
|
+
// GPU加速
|
|
489
|
+
powerPreference: options.gpu ? "high-performance" : "default",
|
|
490
|
+
alpha: true,
|
|
491
|
+
// 对数深度缓冲区 (加载外部模型去除闪烁适用)
|
|
492
|
+
logarithmicDepthBuffer: true
|
|
493
|
+
});
|
|
494
|
+
current_three.renderer.setPixelRatio(window.devicePixelRatio || 1);
|
|
495
|
+
current_three.renderer.setSize(
|
|
496
|
+
current_three.container?.clientWidth || window.innerWidth,
|
|
497
|
+
current_three.container?.clientHeight || window.innerHeight
|
|
498
|
+
);
|
|
499
|
+
current_three.container?.appendChild(current_three.renderer.domElement);
|
|
500
|
+
}
|
|
501
|
+
function init_css2_renderer() {
|
|
502
|
+
current_three.css2Renderer = new CSS2DRenderer();
|
|
503
|
+
current_three.css2Renderer.setSize(
|
|
504
|
+
current_three.container?.clientWidth || window.innerWidth,
|
|
505
|
+
current_three.container?.clientHeight || window.innerHeight
|
|
506
|
+
);
|
|
507
|
+
current_three.css2Renderer.domElement.style.pointerEvents = "none";
|
|
508
|
+
current_three.css2Renderer.domElement.style.position = "absolute";
|
|
509
|
+
current_three.css2Renderer.domElement.style.top = "0px";
|
|
510
|
+
current_three.css2Renderer.domElement.style.zIndex = "1000";
|
|
511
|
+
const parent = current_three.container || document.body;
|
|
512
|
+
parent.appendChild(current_three.css2Renderer.domElement);
|
|
513
|
+
}
|
|
514
|
+
function set_rendering_status(status) {
|
|
515
|
+
is_rendering = status;
|
|
516
|
+
if (status) {
|
|
517
|
+
animation_frame_id = requestAnimationFrame((timestamp) => render_loop(timestamp));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function set_target_fps(fps) {
|
|
521
|
+
target_fps = fps;
|
|
522
|
+
frame_interval = 1e3 / target_fps;
|
|
523
|
+
}
|
|
524
|
+
function render_loop(timestamp, render_action) {
|
|
525
|
+
if (!is_rendering) return;
|
|
526
|
+
if (timestamp - last_render_time >= frame_interval) {
|
|
527
|
+
last_render_time = timestamp;
|
|
528
|
+
try {
|
|
529
|
+
if (current_three.scene && current_three.camera && current_three.renderer) {
|
|
530
|
+
render_action && render_action();
|
|
531
|
+
current_three.css2Renderer?.render(current_three.scene, current_three.camera);
|
|
532
|
+
current_three.renderer.render(current_three.scene, current_three.camera);
|
|
533
|
+
}
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error("Three.js render error:", error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (!current_three.renderer?.xr.enabled) {
|
|
539
|
+
animation_frame_id = requestAnimationFrame((timestamp2) => render_loop(timestamp2, render_action));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function render(render_action) {
|
|
543
|
+
if (current_three.renderer?.xr.enabled) {
|
|
544
|
+
current_three.renderer.setAnimationLoop(() => {
|
|
545
|
+
if (is_rendering) {
|
|
546
|
+
render_action && render_action();
|
|
547
|
+
current_three.css2Renderer?.render(current_three.scene, current_three.camera);
|
|
548
|
+
current_three.renderer?.render(current_three.scene, current_three.camera);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
} else {
|
|
552
|
+
is_rendering = true;
|
|
553
|
+
animation_frame_id = requestAnimationFrame((timestamp) => render_loop(timestamp, render_action));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function resize() {
|
|
557
|
+
const width = current_three.container?.clientWidth || window.innerWidth;
|
|
558
|
+
const heigth = current_three.container?.clientHeight || window.innerHeight;
|
|
559
|
+
current_three.camera.aspect = width / heigth;
|
|
560
|
+
current_three.camera.updateProjectionMatrix();
|
|
561
|
+
current_three.renderer?.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
562
|
+
current_three.renderer?.setSize(width, heigth);
|
|
563
|
+
current_three.css2Renderer?.setSize(width, heigth);
|
|
564
|
+
}
|
|
565
|
+
function set_container(container) {
|
|
566
|
+
if (current_three.container && current_three.renderer) {
|
|
567
|
+
try {
|
|
568
|
+
current_three.container.removeChild(current_three.renderer.domElement);
|
|
569
|
+
} catch (e) {
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (current_three.container && current_three.css2Renderer) {
|
|
573
|
+
try {
|
|
574
|
+
current_three.container.removeChild(current_three.css2Renderer.domElement);
|
|
575
|
+
} catch (e) {
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
current_three.container = container;
|
|
579
|
+
if (current_three.renderer) {
|
|
580
|
+
container.appendChild(current_three.renderer.domElement);
|
|
581
|
+
}
|
|
582
|
+
if (current_three.css2Renderer) {
|
|
583
|
+
container.appendChild(current_three.css2Renderer.domElement);
|
|
584
|
+
}
|
|
585
|
+
resize();
|
|
586
|
+
}
|
|
587
|
+
function cancel_animation() {
|
|
588
|
+
cancelAnimationFrame(animation_frame_id);
|
|
589
|
+
}
|
|
590
|
+
function get_rendering_status() {
|
|
591
|
+
return is_rendering;
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
init_renderer,
|
|
595
|
+
init_css2_renderer,
|
|
596
|
+
set_rendering_status,
|
|
597
|
+
set_target_fps,
|
|
598
|
+
render,
|
|
599
|
+
resize,
|
|
600
|
+
set_container,
|
|
601
|
+
cancel_animation,
|
|
602
|
+
get_rendering_status
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function use_interaction(current_three) {
|
|
606
|
+
const drag_state = {
|
|
607
|
+
is_dragging: false,
|
|
608
|
+
dragged_object: null,
|
|
609
|
+
drag_start: new Vector2(),
|
|
610
|
+
object_initial_position: new Vector3(),
|
|
611
|
+
drag_callbacks: {
|
|
612
|
+
onDragStart: () => {
|
|
613
|
+
},
|
|
614
|
+
onDragEnd: () => {
|
|
615
|
+
},
|
|
616
|
+
onDrag: (position) => {
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
mouse_down_handler: (e) => on_mouse_down(e),
|
|
620
|
+
mouse_move_handler: (e) => on_mouse_move(e),
|
|
621
|
+
mouse_up_handler: (e) => on_mouse_up()
|
|
622
|
+
};
|
|
623
|
+
function get_select_object(e) {
|
|
624
|
+
const dom_element = current_three.renderer?.domElement;
|
|
625
|
+
const rect = dom_element.getBoundingClientRect();
|
|
626
|
+
const x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
627
|
+
const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
628
|
+
const raycaster = new Raycaster();
|
|
629
|
+
raycaster.setFromCamera(new Vector2(x, y), current_three.camera);
|
|
630
|
+
const intersects = raycaster.intersectObjects(current_three.scene.children);
|
|
631
|
+
if (intersects.length > 0) {
|
|
632
|
+
return intersects[0].object;
|
|
633
|
+
} else {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function enable_object_drag(object, options) {
|
|
638
|
+
drag_state.drag_callbacks = {
|
|
639
|
+
onDragStart: options?.onDragStart || (() => {
|
|
640
|
+
}),
|
|
641
|
+
onDragEnd: options?.onDragEnd || (() => {
|
|
642
|
+
}),
|
|
643
|
+
onDrag: options?.onDrag || (() => {
|
|
644
|
+
})
|
|
645
|
+
};
|
|
646
|
+
drag_state.dragged_object = object;
|
|
647
|
+
const dom_element = current_three.renderer?.domElement;
|
|
648
|
+
if (dom_element) {
|
|
649
|
+
dom_element.addEventListener("mousedown", drag_state.mouse_down_handler);
|
|
650
|
+
dom_element.addEventListener("mousemove", drag_state.mouse_move_handler);
|
|
651
|
+
dom_element.addEventListener("mouseup", drag_state.mouse_up_handler);
|
|
652
|
+
dom_element.addEventListener("mouseleave", drag_state.mouse_up_handler);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function disable_object_drag() {
|
|
656
|
+
const dom_element = current_three.renderer?.domElement;
|
|
657
|
+
if (dom_element) {
|
|
658
|
+
dom_element.removeEventListener("mousedown", drag_state.mouse_down_handler);
|
|
659
|
+
dom_element.removeEventListener("mousemove", drag_state.mouse_move_handler);
|
|
660
|
+
dom_element.removeEventListener("mouseup", drag_state.mouse_up_handler);
|
|
661
|
+
dom_element.removeEventListener("mouseleave", drag_state.mouse_up_handler);
|
|
662
|
+
}
|
|
663
|
+
drag_state.is_dragging = false;
|
|
664
|
+
drag_state.dragged_object = null;
|
|
665
|
+
}
|
|
666
|
+
function on_mouse_down(e) {
|
|
667
|
+
if (!drag_state.dragged_object || !current_three.camera || !current_three.scene) return;
|
|
668
|
+
const dom_element = current_three.renderer?.domElement;
|
|
669
|
+
if (!dom_element) return;
|
|
670
|
+
const rect = dom_element.getBoundingClientRect();
|
|
671
|
+
drag_state.drag_start.x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
672
|
+
drag_state.drag_start.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
673
|
+
const raycaster = new Raycaster();
|
|
674
|
+
raycaster.setFromCamera(drag_state.drag_start, current_three.camera);
|
|
675
|
+
const intersects = raycaster.intersectObjects([drag_state.dragged_object]);
|
|
676
|
+
if (intersects.length > 0) {
|
|
677
|
+
drag_state.is_dragging = true;
|
|
678
|
+
drag_state.object_initial_position.copy(drag_state.dragged_object.position);
|
|
679
|
+
drag_state.drag_callbacks.onDragStart();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function on_mouse_move(e) {
|
|
683
|
+
if (!drag_state.is_dragging || !drag_state.dragged_object || !current_three.camera) return;
|
|
684
|
+
const dom_element = current_three.renderer?.domElement;
|
|
685
|
+
if (!dom_element) return;
|
|
686
|
+
const rect = dom_element.getBoundingClientRect();
|
|
687
|
+
const mouse_x = (e.clientX - rect.left) / rect.width * 2 - 1;
|
|
688
|
+
const mouse_y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
|
689
|
+
const offset_x = mouse_x - drag_state.drag_start.x;
|
|
690
|
+
const offset_y = mouse_y - drag_state.drag_start.y;
|
|
691
|
+
drag_state.dragged_object.position.x = drag_state.object_initial_position.x + offset_x * 10;
|
|
692
|
+
drag_state.dragged_object.position.y = drag_state.object_initial_position.y - offset_y * 10;
|
|
693
|
+
drag_state.drag_callbacks.onDrag({
|
|
694
|
+
x: drag_state.dragged_object.position.x,
|
|
695
|
+
y: drag_state.dragged_object.position.y,
|
|
696
|
+
z: drag_state.dragged_object.position.z
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
function on_mouse_up(e) {
|
|
700
|
+
if (drag_state.is_dragging) {
|
|
701
|
+
drag_state.is_dragging = false;
|
|
702
|
+
drag_state.drag_callbacks.onDragEnd();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function enable_touch_support() {
|
|
706
|
+
const dom_element = current_three.renderer?.domElement;
|
|
707
|
+
if (!dom_element) return;
|
|
708
|
+
dom_element.style.touchAction = "none";
|
|
709
|
+
if (current_three.controls) ;
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
get_select_object,
|
|
713
|
+
enable_object_drag,
|
|
714
|
+
disable_object_drag,
|
|
715
|
+
enable_touch_support
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function use_loader(current_three) {
|
|
719
|
+
function use_draco_loader(gltfLoader) {
|
|
720
|
+
const dracoLoader = new DRACOLoader();
|
|
721
|
+
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf");
|
|
722
|
+
dracoLoader.setDecoderConfig({ type: "wasm" });
|
|
723
|
+
gltfLoader.setDRACOLoader(dracoLoader);
|
|
724
|
+
}
|
|
725
|
+
function load_gltf_model(url, options) {
|
|
726
|
+
const loader = new GLTFLoader();
|
|
727
|
+
const loadingState = {
|
|
728
|
+
progress: 0,
|
|
729
|
+
loaded: false,
|
|
730
|
+
error: null,
|
|
731
|
+
model: null
|
|
732
|
+
};
|
|
733
|
+
use_draco_loader(loader);
|
|
734
|
+
loader.load(
|
|
735
|
+
url,
|
|
736
|
+
(gltf) => {
|
|
737
|
+
loadingState.loaded = true;
|
|
738
|
+
loadingState.progress = 1;
|
|
739
|
+
loadingState.model = gltf.scene;
|
|
740
|
+
current_three.scene?.add(gltf.scene);
|
|
741
|
+
},
|
|
742
|
+
(xhr) => {
|
|
743
|
+
if (xhr.total) {
|
|
744
|
+
loadingState.progress = xhr.loaded / xhr.total;
|
|
745
|
+
options?.onProgress?.(loadingState.progress);
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
(error) => {
|
|
749
|
+
loadingState.error = error;
|
|
750
|
+
options?.onError?.(error);
|
|
751
|
+
}
|
|
752
|
+
);
|
|
753
|
+
return loadingState;
|
|
754
|
+
}
|
|
755
|
+
function load_obj_model(url, mtlUrl, options) {
|
|
756
|
+
const loadingState = {
|
|
757
|
+
progress: 0,
|
|
758
|
+
loaded: false,
|
|
759
|
+
error: null,
|
|
760
|
+
model: null
|
|
761
|
+
};
|
|
762
|
+
if (mtlUrl) {
|
|
763
|
+
const mtlLoader = new MTLLoader();
|
|
764
|
+
mtlLoader.load(
|
|
765
|
+
mtlUrl,
|
|
766
|
+
(materials) => {
|
|
767
|
+
materials.preload();
|
|
768
|
+
const objLoader = new OBJLoader();
|
|
769
|
+
objLoader.setMaterials(materials);
|
|
770
|
+
objLoader.load(
|
|
771
|
+
url,
|
|
772
|
+
(object) => {
|
|
773
|
+
loadingState.loaded = true;
|
|
774
|
+
loadingState.progress = 1;
|
|
775
|
+
loadingState.model = object;
|
|
776
|
+
current_three.scene?.add(object);
|
|
777
|
+
},
|
|
778
|
+
(xhr) => {
|
|
779
|
+
if (xhr.total) {
|
|
780
|
+
loadingState.progress = xhr.loaded / xhr.total;
|
|
781
|
+
options?.onProgress?.(loadingState.progress);
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
(error) => {
|
|
785
|
+
loadingState.error = error;
|
|
786
|
+
options?.onError?.(error);
|
|
787
|
+
}
|
|
788
|
+
);
|
|
789
|
+
},
|
|
790
|
+
(xhr) => {
|
|
791
|
+
if (xhr.total) {
|
|
792
|
+
loadingState.progress = xhr.loaded / xhr.total * 0.5;
|
|
793
|
+
options?.onProgress?.(loadingState.progress);
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
(error) => {
|
|
797
|
+
loadingState.error = error;
|
|
798
|
+
options?.onError?.(error);
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
} else {
|
|
802
|
+
const objLoader = new OBJLoader();
|
|
803
|
+
objLoader.load(
|
|
804
|
+
url,
|
|
805
|
+
(object) => {
|
|
806
|
+
loadingState.loaded = true;
|
|
807
|
+
loadingState.progress = 1;
|
|
808
|
+
loadingState.model = object;
|
|
809
|
+
current_three.scene?.add(object);
|
|
810
|
+
},
|
|
811
|
+
(xhr) => {
|
|
812
|
+
if (xhr.total) {
|
|
813
|
+
loadingState.progress = xhr.loaded / xhr.total;
|
|
814
|
+
options?.onProgress?.(loadingState.progress);
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
(error) => {
|
|
818
|
+
loadingState.error = error;
|
|
819
|
+
options?.onError?.(error);
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
return loadingState;
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
use_draco_loader,
|
|
827
|
+
load_gltf_model,
|
|
828
|
+
load_obj_model
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function use_resources(current_three) {
|
|
832
|
+
function clear_resources() {
|
|
833
|
+
if (current_three.scene) {
|
|
834
|
+
while (current_three.scene.children.length > 0) {
|
|
835
|
+
remove_object(current_three.scene.children[0]);
|
|
836
|
+
}
|
|
837
|
+
current_three.scene.background = null;
|
|
838
|
+
current_three.scene.environment?.dispose();
|
|
839
|
+
current_three.scene.clear();
|
|
840
|
+
current_three.scene = void 0;
|
|
841
|
+
}
|
|
842
|
+
current_three.camera?.remove();
|
|
843
|
+
current_three.camera && (current_three.camera = void 0);
|
|
844
|
+
current_three.container && (current_three.container = void 0);
|
|
845
|
+
current_three.controls?.dispose?.();
|
|
846
|
+
current_three.controls && (current_three.controls = void 0);
|
|
847
|
+
if (current_three.css2Renderer) {
|
|
848
|
+
current_three.css2Renderer.domElement.remove();
|
|
849
|
+
current_three.css2Renderer = void 0;
|
|
850
|
+
}
|
|
851
|
+
clear_renderer();
|
|
852
|
+
Cache.clear();
|
|
853
|
+
}
|
|
854
|
+
function clear_renderer() {
|
|
855
|
+
if (current_three.renderer) {
|
|
856
|
+
current_three.renderer.renderLists?.dispose();
|
|
857
|
+
current_three.renderer.dispose();
|
|
858
|
+
current_three.renderer.forceContextLoss();
|
|
859
|
+
try {
|
|
860
|
+
current_three.renderer.domElement.remove();
|
|
861
|
+
} catch (e) {
|
|
862
|
+
}
|
|
863
|
+
current_three.renderer = void 0;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function remove_object(object) {
|
|
867
|
+
if (!object) return;
|
|
868
|
+
if (object.children && object.children.length > 0) {
|
|
869
|
+
for (let i = object.children.length - 1; i >= 0; i--) {
|
|
870
|
+
remove_object(object.children[i]);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if ("geometry" in object && object.geometry) {
|
|
874
|
+
const geometry = object.geometry;
|
|
875
|
+
geometry.dispose?.();
|
|
876
|
+
}
|
|
877
|
+
if ("material" in object && object.material) {
|
|
878
|
+
if (Array.isArray(object.material)) {
|
|
879
|
+
object.material.forEach((material) => {
|
|
880
|
+
material.dispose?.();
|
|
881
|
+
if (material.map) material.map.dispose?.();
|
|
882
|
+
if (material.normalMap) material.normalMap.dispose?.();
|
|
883
|
+
if (material.specularMap) material.specularMap.dispose?.();
|
|
884
|
+
if (material.aoMap) material.aoMap.dispose?.();
|
|
885
|
+
});
|
|
886
|
+
} else {
|
|
887
|
+
const material = object.material;
|
|
888
|
+
material.dispose?.();
|
|
889
|
+
if (material.map) material.map.dispose?.();
|
|
890
|
+
if (material.normalMap) material.normalMap.dispose?.();
|
|
891
|
+
if (material.specularMap) material.specularMap.dispose?.();
|
|
892
|
+
if (material.aoMap) material.aoMap.dispose?.();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if ("texture" in object && object.texture) {
|
|
896
|
+
const texture = object.texture;
|
|
897
|
+
texture.dispose?.();
|
|
898
|
+
}
|
|
899
|
+
object.clear?.();
|
|
900
|
+
object.dispose?.();
|
|
901
|
+
object.parent?.remove(object);
|
|
902
|
+
object = null;
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
clear_resources,
|
|
906
|
+
clear_renderer,
|
|
907
|
+
remove_object
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function use_box_helper(current_three, color = "#00ffff") {
|
|
911
|
+
const helper = new BoxHelper(new Object3D(), color);
|
|
912
|
+
function add_to_scene(scene) {
|
|
913
|
+
scene.add(helper);
|
|
914
|
+
}
|
|
915
|
+
function set_box_frame(obj) {
|
|
916
|
+
helper.setFromObject(obj);
|
|
917
|
+
set_visible(true);
|
|
918
|
+
}
|
|
919
|
+
function set_visible(visible) {
|
|
920
|
+
helper.visible = visible;
|
|
921
|
+
}
|
|
922
|
+
function destroy() {
|
|
923
|
+
if (helper.parent !== null) {
|
|
924
|
+
helper.parent.remove(helper);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
helper,
|
|
929
|
+
add_to_scene,
|
|
930
|
+
set_box_frame,
|
|
931
|
+
set_visible,
|
|
932
|
+
destroy
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function use_effect_composer(current_three) {
|
|
936
|
+
const composers = [];
|
|
937
|
+
function get_container_size() {
|
|
938
|
+
return {
|
|
939
|
+
width: current_three.container?.scrollWidth || window.innerWidth,
|
|
940
|
+
height: current_three.container?.scrollHeight || window.innerHeight
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function enable_effect(use_ssao = true, ssao_options) {
|
|
944
|
+
if (!current_three.renderer) {
|
|
945
|
+
console.error("Renderer is not initialized");
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
const composer = new EffectComposer(current_three.renderer);
|
|
949
|
+
const render_pass = new RenderPass(current_three.scene, current_three.camera);
|
|
950
|
+
composer.addPass(render_pass);
|
|
951
|
+
if (use_ssao && current_three.scene && current_three.camera) {
|
|
952
|
+
const ssao_pass = new SSAOPass(
|
|
953
|
+
current_three.scene,
|
|
954
|
+
current_three.camera,
|
|
955
|
+
get_container_size().width,
|
|
956
|
+
get_container_size().height
|
|
957
|
+
);
|
|
958
|
+
ssao_pass.kernelRadius = ssao_options?.kernel_radius || 0.5;
|
|
959
|
+
ssao_pass.minDistance = ssao_options?.min_distance || 1e-3;
|
|
960
|
+
ssao_pass.maxDistance = ssao_options?.max_distance || 0.025;
|
|
961
|
+
composer.addPass(ssao_pass);
|
|
962
|
+
}
|
|
963
|
+
composers.push(composer);
|
|
964
|
+
return composer;
|
|
965
|
+
}
|
|
966
|
+
function enable_fxaa(composer) {
|
|
967
|
+
if (!composer || !current_three.renderer) {
|
|
968
|
+
console.error("Composer or renderer is not initialized");
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const { width, height } = get_container_size();
|
|
972
|
+
const fxaa_pass = new ShaderPass(FXAAShader);
|
|
973
|
+
fxaa_pass.uniforms["resolution"].value.set(
|
|
974
|
+
1 / (width * current_three.renderer.getPixelRatio()),
|
|
975
|
+
1 / (height * current_three.renderer.getPixelRatio())
|
|
976
|
+
);
|
|
977
|
+
fxaa_pass.renderToScreen = true;
|
|
978
|
+
composer.addPass(fxaa_pass);
|
|
979
|
+
}
|
|
980
|
+
function enable_unreal_bloom(composer, strength = 0.4, radius = 1.5, threshold = 0.2) {
|
|
981
|
+
if (!composer) {
|
|
982
|
+
console.error("Composer is not initialized");
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
const { width, height } = get_container_size();
|
|
986
|
+
const bloom_pass = new UnrealBloomPass(
|
|
987
|
+
new Vector2(width, height),
|
|
988
|
+
strength,
|
|
989
|
+
// 强度:光晕扩散程度
|
|
990
|
+
radius,
|
|
991
|
+
// 半径:光晕范围
|
|
992
|
+
threshold
|
|
993
|
+
// 阈值:亮度达到多少时触发光晕
|
|
994
|
+
);
|
|
995
|
+
composer.addPass(bloom_pass);
|
|
996
|
+
return bloom_pass;
|
|
997
|
+
}
|
|
998
|
+
function enable_outline(composer) {
|
|
999
|
+
if (!composer || !current_three.scene || !current_three.camera) {
|
|
1000
|
+
console.error("Composer, scene or camera is not initialized");
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
const { width, height } = get_container_size();
|
|
1004
|
+
const outline_pass = new OutlinePass(
|
|
1005
|
+
new Vector2(width, height),
|
|
1006
|
+
current_three.scene,
|
|
1007
|
+
current_three.camera
|
|
1008
|
+
);
|
|
1009
|
+
composer.addPass(outline_pass);
|
|
1010
|
+
return outline_pass;
|
|
1011
|
+
}
|
|
1012
|
+
function set_out_line(outline_pass, objects, out_line_options = {}) {
|
|
1013
|
+
if (!outline_pass) {
|
|
1014
|
+
console.error("OutlinePass is not initialized");
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const default_options = {
|
|
1018
|
+
pulse_period: 5,
|
|
1019
|
+
// 脉冲周期(0为不闪烁),数值越大,闪烁越慢
|
|
1020
|
+
visible_edge_color: "#f44d0b",
|
|
1021
|
+
// 可见边缘颜色
|
|
1022
|
+
hidden_edge_color: "#000",
|
|
1023
|
+
// 阴影颜色
|
|
1024
|
+
use_pattern_texture: false,
|
|
1025
|
+
// 使用纹理覆盖
|
|
1026
|
+
edge_strength: 3,
|
|
1027
|
+
// 轮廓强度
|
|
1028
|
+
edge_glow: 0.5,
|
|
1029
|
+
// 边缘辉光
|
|
1030
|
+
edge_thickness: 3
|
|
1031
|
+
// 轮廓厚度
|
|
1032
|
+
};
|
|
1033
|
+
const options = { ...default_options, ...out_line_options };
|
|
1034
|
+
Object.keys(options).forEach((key) => {
|
|
1035
|
+
if (!["visible_edge_color", "hidden_edge_color"].includes(key)) {
|
|
1036
|
+
outline_pass[key] = options[key];
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
if (options.visible_edge_color) {
|
|
1040
|
+
outline_pass.visibleEdgeColor.set(options.visible_edge_color);
|
|
1041
|
+
}
|
|
1042
|
+
if (options.hidden_edge_color) {
|
|
1043
|
+
outline_pass.hiddenEdgeColor.set(options.hidden_edge_color);
|
|
1044
|
+
}
|
|
1045
|
+
outline_pass.selectedObjects = objects;
|
|
1046
|
+
}
|
|
1047
|
+
function enable_film_effect(composer, options = {}) {
|
|
1048
|
+
if (!composer) {
|
|
1049
|
+
console.error("Composer is not initialized");
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
const default_options = {
|
|
1053
|
+
noise_intensity: 0.025,
|
|
1054
|
+
grayscale: false
|
|
1055
|
+
};
|
|
1056
|
+
const { noise_intensity, grayscale } = {
|
|
1057
|
+
...default_options,
|
|
1058
|
+
...options
|
|
1059
|
+
};
|
|
1060
|
+
const film_pass = new FilmPass(
|
|
1061
|
+
noise_intensity,
|
|
1062
|
+
// 颗粒强度
|
|
1063
|
+
grayscale
|
|
1064
|
+
// 灰度模式
|
|
1065
|
+
);
|
|
1066
|
+
film_pass.renderToScreen = true;
|
|
1067
|
+
composer.addPass(film_pass);
|
|
1068
|
+
return film_pass;
|
|
1069
|
+
}
|
|
1070
|
+
function enable_grayscale(composer) {
|
|
1071
|
+
if (!composer) {
|
|
1072
|
+
console.error("Composer is not initialized");
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
const grayscale_pass = new ShaderPass(GrayscaleShader);
|
|
1076
|
+
grayscale_pass.renderToScreen = true;
|
|
1077
|
+
composer.addPass(grayscale_pass);
|
|
1078
|
+
return grayscale_pass;
|
|
1079
|
+
}
|
|
1080
|
+
function update_all_composers_size() {
|
|
1081
|
+
const { width, height } = get_container_size();
|
|
1082
|
+
composers.forEach((composer) => {
|
|
1083
|
+
if (composer) {
|
|
1084
|
+
composer.setSize(width, height);
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
function clear_composers() {
|
|
1089
|
+
composers.forEach((composer) => {
|
|
1090
|
+
if (composer) {
|
|
1091
|
+
composer.dispose();
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
composers.length = 0;
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
enable_effect,
|
|
1098
|
+
enable_fxaa,
|
|
1099
|
+
enable_unreal_bloom,
|
|
1100
|
+
enable_outline,
|
|
1101
|
+
set_out_line,
|
|
1102
|
+
enable_film_effect,
|
|
1103
|
+
enable_grayscale,
|
|
1104
|
+
update_all_composers_size,
|
|
1105
|
+
clear_composers
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
const defaultThreeOption = {
|
|
1109
|
+
scenebgcolor: "#000",
|
|
1110
|
+
scenebg: void 0,
|
|
1111
|
+
camera_option: {
|
|
1112
|
+
position: { x: 50, y: 50, z: 0 },
|
|
1113
|
+
fov: 50,
|
|
1114
|
+
near: 0.1,
|
|
1115
|
+
far: 800
|
|
1116
|
+
},
|
|
1117
|
+
camera_type: "perspective",
|
|
1118
|
+
css2_render: false,
|
|
1119
|
+
controls: true,
|
|
1120
|
+
axes_helper: false,
|
|
1121
|
+
default_lighting: true
|
|
1122
|
+
};
|
|
1123
|
+
function use_three(options) {
|
|
1124
|
+
let current_three = {};
|
|
1125
|
+
options = { ...defaultThreeOption, ...options };
|
|
1126
|
+
let vr_dom = void 0;
|
|
1127
|
+
const { create_perspective_camera, create_orthographic_camera, set_camera_preset, orbit_controls, axes_helper, reset_camera } = use_camera(current_three);
|
|
1128
|
+
const { add_default_lighting, add_ambient_light, add_directional_light, add_point_light, add_spot_light, enable_shadow } = use_lighting(current_three);
|
|
1129
|
+
const { init_renderer, init_css2_renderer, set_rendering_status, set_target_fps, render, resize, set_container } = use_renderer(current_three);
|
|
1130
|
+
const { get_select_object, enable_object_drag, disable_object_drag, enable_touch_support } = use_interaction(current_three);
|
|
1131
|
+
const { load_gltf_model, load_obj_model, use_draco_loader } = use_loader(current_three);
|
|
1132
|
+
const { clear_resources } = use_resources(current_three);
|
|
1133
|
+
const { enable_effect, enable_fxaa, enable_unreal_bloom, enable_outline, set_out_line } = use_effect_composer(current_three);
|
|
1134
|
+
function create_box_helper(color = "#00ffff") {
|
|
1135
|
+
return use_box_helper(current_three, color);
|
|
1136
|
+
}
|
|
1137
|
+
onMounted(() => {
|
|
1138
|
+
on(window, "resize", resize);
|
|
1139
|
+
});
|
|
1140
|
+
onBeforeUnmount(() => {
|
|
1141
|
+
off(window, "resize", resize);
|
|
1142
|
+
clear_resources();
|
|
1143
|
+
});
|
|
1144
|
+
function init_three() {
|
|
1145
|
+
try {
|
|
1146
|
+
current_three.scene = new Scene();
|
|
1147
|
+
const { scenebgcolor, scenebg } = options;
|
|
1148
|
+
scenebgcolor && (current_three.scene.background = new Color(scenebgcolor));
|
|
1149
|
+
scenebg && (current_three.scene.background = scenebg);
|
|
1150
|
+
init_renderer(options);
|
|
1151
|
+
options.css2_render && init_css2_renderer();
|
|
1152
|
+
if (options.camera_type === "orthographic") {
|
|
1153
|
+
create_orthographic_camera();
|
|
1154
|
+
} else {
|
|
1155
|
+
create_perspective_camera(options);
|
|
1156
|
+
}
|
|
1157
|
+
options.default_lighting && add_default_lighting();
|
|
1158
|
+
options.controls && orbit_controls();
|
|
1159
|
+
options.axes_helper && axes_helper();
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
console.error("Three.js initialization error:", error);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function reset() {
|
|
1165
|
+
reset_camera(options);
|
|
1166
|
+
}
|
|
1167
|
+
function enabled_vr() {
|
|
1168
|
+
if (current_three.renderer) {
|
|
1169
|
+
vr_dom = VRButton.createButton(current_three.renderer);
|
|
1170
|
+
if (vr_dom) {
|
|
1171
|
+
document.body.appendChild(vr_dom);
|
|
1172
|
+
}
|
|
1173
|
+
current_three.renderer.xr.enabled = true;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function disabled_vr() {
|
|
1177
|
+
if (current_three.renderer) {
|
|
1178
|
+
vr_dom?.remove();
|
|
1179
|
+
current_three.renderer.xr.enabled = false;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function create_tool_tip(obj, options2) {
|
|
1183
|
+
const tool_tip = new ObjectToolTip();
|
|
1184
|
+
tool_tip.create(obj, options2);
|
|
1185
|
+
return tool_tip;
|
|
1186
|
+
}
|
|
1187
|
+
function create_tool_tip_instance() {
|
|
1188
|
+
return new ObjectToolTip();
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
instance: current_three,
|
|
1192
|
+
init_three,
|
|
1193
|
+
set_container,
|
|
1194
|
+
create_perspective_camera: () => create_perspective_camera(options),
|
|
1195
|
+
create_orthographic_camera,
|
|
1196
|
+
set_camera_preset,
|
|
1197
|
+
add_default_lighting,
|
|
1198
|
+
add_ambient_light,
|
|
1199
|
+
add_directional_light,
|
|
1200
|
+
add_point_light,
|
|
1201
|
+
add_spot_light,
|
|
1202
|
+
set_rendering_status,
|
|
1203
|
+
set_target_fps,
|
|
1204
|
+
load_gltf_model,
|
|
1205
|
+
load_obj_model,
|
|
1206
|
+
enable_object_drag,
|
|
1207
|
+
disable_object_drag,
|
|
1208
|
+
enable_touch_support,
|
|
1209
|
+
utils: three_utils,
|
|
1210
|
+
dispose_three: clear_resources,
|
|
1211
|
+
render,
|
|
1212
|
+
get_select_object,
|
|
1213
|
+
enabled_vr,
|
|
1214
|
+
disabled_vr,
|
|
1215
|
+
use_draco_loader,
|
|
1216
|
+
reset,
|
|
1217
|
+
resize,
|
|
1218
|
+
enable_shadow,
|
|
1219
|
+
create_tool_tip,
|
|
1220
|
+
create_tool_tip_instance,
|
|
1221
|
+
// 边界框辅助
|
|
1222
|
+
create_box_helper,
|
|
1223
|
+
// 后期处理
|
|
1224
|
+
enable_effect,
|
|
1225
|
+
enable_fxaa,
|
|
1226
|
+
enable_unreal_bloom,
|
|
1227
|
+
enable_outline,
|
|
1228
|
+
set_out_line
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
export {
|
|
1232
|
+
use_three
|
|
1233
|
+
};
|