easy-threesdk 1.0.0
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/README.md +166 -0
- package/index.js +898 -0
- package/package.json +15 -0
- package/plugins/LabelControl.js +202 -0
- package/plugins/ModelControl.js +281 -0
package/index.js
ADDED
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
3
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
|
+
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
5
|
+
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader.js";
|
|
6
|
+
import {
|
|
7
|
+
CSS3DRenderer,
|
|
8
|
+
CSS3DObject,
|
|
9
|
+
} from "three/examples/jsm/renderers/CSS3DRenderer.js";
|
|
10
|
+
import { ThreeMFLoader } from "three/examples/jsm/Addons.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} CameraConfig
|
|
14
|
+
* @property {THREE.Vector3} position 相机位置
|
|
15
|
+
* @property {THREE.Vector3} lookAt 相机朝向
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} ControlConfig
|
|
19
|
+
* @property {boolean} enableDamping 是否启用阻尼
|
|
20
|
+
* @property {number} dampingFactor 阻尼因子
|
|
21
|
+
* @property {number} minDistance 最小距离
|
|
22
|
+
* @property {number} maxDistance 最大距离
|
|
23
|
+
* @property {number} maxPolarAngle 最大极角
|
|
24
|
+
* @property {THREE.Vector3} target 目标位置
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} LightConfig
|
|
28
|
+
* @property {THREE.Color} ambientLight 环境光颜色
|
|
29
|
+
* @property {number} ambientLightIntensity 环境光强度
|
|
30
|
+
* @property {THREE.Color} directionalLightColor 方向光颜色
|
|
31
|
+
* @property {number} directionalLightIntensity 方向光强度
|
|
32
|
+
* @property {THREE.Color} rimLightColor 边缘光颜色
|
|
33
|
+
* @property {number} rimLightIntensity 边缘光强度
|
|
34
|
+
* @property {THREE.Vector3} rimLightPosition 边缘光位置
|
|
35
|
+
* @property {THREE.Vector3} mainLightPosition 主光位置
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
class ThreeSdk {
|
|
39
|
+
//插件Map
|
|
40
|
+
_PluginsManager = new Map();
|
|
41
|
+
|
|
42
|
+
// 相机配置
|
|
43
|
+
_CameraConfig = {
|
|
44
|
+
fixedCameraPosition: null,
|
|
45
|
+
rotateAroundSelf: false, // 是否围绕自身旋转
|
|
46
|
+
isRoamMode: false, // 是否为漫游模式
|
|
47
|
+
isCameraAnimating: false, // 是否正在执行相机动画
|
|
48
|
+
cameraAnimationId: null, // 动画ID,用于取消动画
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 漫游模式配置
|
|
52
|
+
_RoamConfig = {
|
|
53
|
+
cameraHeight: 2, // 漫游相机高度(距离地面的高度)
|
|
54
|
+
// 地面圆圈
|
|
55
|
+
groundCircle: null, //地面圆圈Mesh 网格对象
|
|
56
|
+
moveSpeed: 0.1,
|
|
57
|
+
rotateSpeed: 0.002, //旋转视角的速度
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// 轨道控制器配置
|
|
61
|
+
_OrbitControlsConfig = {
|
|
62
|
+
enablePan: null,
|
|
63
|
+
enableZoom: null,
|
|
64
|
+
minDistance: null,
|
|
65
|
+
maxDistance: null,
|
|
66
|
+
maxPolarAngle: null,
|
|
67
|
+
target: null,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//全景球体
|
|
71
|
+
_PanoramaSphere = null;
|
|
72
|
+
|
|
73
|
+
// 3d场景组 map
|
|
74
|
+
_ThreeGroupMap = new Map();
|
|
75
|
+
|
|
76
|
+
// 地面平面网格 可以是一个Mesh,也可以是一个 Mesh List,Mesh List 是多个Mesh的集合
|
|
77
|
+
_GroundPlane = null;
|
|
78
|
+
|
|
79
|
+
//键盘事件处理
|
|
80
|
+
_KeyboardEventHandler = {
|
|
81
|
+
w: false,
|
|
82
|
+
a: false,
|
|
83
|
+
s: false,
|
|
84
|
+
d: false,
|
|
85
|
+
ArrowUp: false,
|
|
86
|
+
ArrowDown: false,
|
|
87
|
+
ArrowLeft: false,
|
|
88
|
+
ArrowRight: false,
|
|
89
|
+
isDragging: false,
|
|
90
|
+
previousMousePosition: { x: 0, y: 0 },
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
constructor(container) {
|
|
94
|
+
this.container = container;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 初始化3d场景
|
|
98
|
+
* @param {CameraConfig} cameraConfig 相机配置
|
|
99
|
+
* @param {ControlConfig} controlConfig 控制器配置
|
|
100
|
+
* @param {LightConfig} lightConfig 灯光配置
|
|
101
|
+
*/
|
|
102
|
+
init(cameraConfig = {}, controlConfig = {}, lightConfig = {}) {
|
|
103
|
+
let { position, lookAt } = Object.assign(
|
|
104
|
+
{
|
|
105
|
+
position: new THREE.Vector3(0, 280, 320),
|
|
106
|
+
lookAt: new THREE.Vector3(0, 0, 0),
|
|
107
|
+
},
|
|
108
|
+
cameraConfig
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
let {
|
|
112
|
+
enableDamping,
|
|
113
|
+
dampingFactor,
|
|
114
|
+
minDistance,
|
|
115
|
+
maxDistance,
|
|
116
|
+
maxPolarAngle,
|
|
117
|
+
target,
|
|
118
|
+
} = Object.assign(
|
|
119
|
+
{
|
|
120
|
+
enableDamping: true,
|
|
121
|
+
dampingFactor: 0.05,
|
|
122
|
+
minDistance: 50,
|
|
123
|
+
maxDistance: 800,
|
|
124
|
+
maxPolarAngle: Math.PI / 2,
|
|
125
|
+
target: new THREE.Vector3(0, 0, 0),
|
|
126
|
+
},
|
|
127
|
+
controlConfig
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let {
|
|
131
|
+
ambientLight,
|
|
132
|
+
ambientLightIntensity,
|
|
133
|
+
directionalLightColor,
|
|
134
|
+
directionalLightIntensity,
|
|
135
|
+
rimLightColor,
|
|
136
|
+
rimLightIntensity,
|
|
137
|
+
rimLightPosition,
|
|
138
|
+
mainLightPosition,
|
|
139
|
+
} = Object.assign(
|
|
140
|
+
{
|
|
141
|
+
ambientLight: 0xffffff,
|
|
142
|
+
ambientLightIntensity: 0.5,
|
|
143
|
+
directionalLightColor: 0xa5f3fc,
|
|
144
|
+
directionalLightIntensity: 1.5,
|
|
145
|
+
rimLightColor: 0x0ea5e9,
|
|
146
|
+
rimLightIntensity: 3,
|
|
147
|
+
rimLightPosition: new THREE.Vector3(0, 100, -100),
|
|
148
|
+
mainLightPosition: new THREE.Vector3(50, 100, 50),
|
|
149
|
+
},
|
|
150
|
+
lightConfig
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
//初始化场景
|
|
154
|
+
this.scene = new THREE.Scene();
|
|
155
|
+
const width = this.container.clientWidth;
|
|
156
|
+
const height = this.container.clientHeight;
|
|
157
|
+
|
|
158
|
+
//初始化摄像机
|
|
159
|
+
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
|
160
|
+
this.camera.position.set(position.x, position.y, position.z);
|
|
161
|
+
this.camera.lookAt(lookAt.x, lookAt.y, lookAt.z);
|
|
162
|
+
|
|
163
|
+
//初始化渲染器
|
|
164
|
+
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
165
|
+
this.renderer.setSize(width, height);
|
|
166
|
+
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
167
|
+
this.container.appendChild(this.renderer.domElement);
|
|
168
|
+
|
|
169
|
+
//初始化控制器
|
|
170
|
+
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
171
|
+
this.controls.target.set(target.x, target.y, target.z);
|
|
172
|
+
this.controls.enableDamping = enableDamping;
|
|
173
|
+
this.controls.dampingFactor = dampingFactor;
|
|
174
|
+
this.controls.minDistance = minDistance;
|
|
175
|
+
this.controls.maxDistance = maxDistance;
|
|
176
|
+
this.controls.maxPolarAngle = maxPolarAngle;
|
|
177
|
+
|
|
178
|
+
// 保存 OrbitControls 的原始配置(用于退出第一人称模式时恢复)
|
|
179
|
+
this._OrbitControlsConfig.enablePan = this.controls.enablePan;
|
|
180
|
+
this._OrbitControlsConfig.enableZoom = this.controls.enableZoom;
|
|
181
|
+
this._OrbitControlsConfig.minDistance = this.controls.minDistance;
|
|
182
|
+
this._OrbitControlsConfig.maxDistance = this.controls.maxDistance;
|
|
183
|
+
this._OrbitControlsConfig.maxPolarAngle = this.controls.maxPolarAngle;
|
|
184
|
+
this._OrbitControlsConfig.target = this.controls.target.clone();
|
|
185
|
+
|
|
186
|
+
// 初始化场景灯光
|
|
187
|
+
this.scene.add(new THREE.AmbientLight(ambientLight, ambientLightIntensity)); // 调亮环境光
|
|
188
|
+
const mainLight = new THREE.DirectionalLight(
|
|
189
|
+
directionalLightColor,
|
|
190
|
+
directionalLightIntensity
|
|
191
|
+
);
|
|
192
|
+
mainLight.position.set(
|
|
193
|
+
mainLightPosition.x,
|
|
194
|
+
mainLightPosition.y,
|
|
195
|
+
mainLightPosition.z
|
|
196
|
+
);
|
|
197
|
+
this.scene.add(mainLight);
|
|
198
|
+
const rimLight = new THREE.SpotLight(rimLightColor, rimLightIntensity);
|
|
199
|
+
rimLight.position.set(
|
|
200
|
+
rimLightPosition.x,
|
|
201
|
+
rimLightPosition.y,
|
|
202
|
+
rimLightPosition.z
|
|
203
|
+
);
|
|
204
|
+
this.scene.add(rimLight);
|
|
205
|
+
|
|
206
|
+
// 初始化射线 Raycaster
|
|
207
|
+
this.raycaster = new THREE.Raycaster();
|
|
208
|
+
|
|
209
|
+
window.addEventListener("resize", () => {
|
|
210
|
+
const w = this.container.clientWidth;
|
|
211
|
+
const h = this.container.clientHeight;
|
|
212
|
+
this.camera.aspect = w / h;
|
|
213
|
+
this.camera.updateProjectionMatrix();
|
|
214
|
+
this.renderer.setSize(w, h);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
this._handleWindowEvent();
|
|
218
|
+
this._animate();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 注册插件
|
|
223
|
+
* @param {Array<Object>|Object} plugins 插件列表或单个插件
|
|
224
|
+
* @returns {void}
|
|
225
|
+
*/
|
|
226
|
+
plugins(plugins = []) {
|
|
227
|
+
if (Array.isArray(plugins)) {
|
|
228
|
+
plugins.forEach((plugin) => {
|
|
229
|
+
this._PluginsManager.set(
|
|
230
|
+
plugin.name || plugin.constructor.name,
|
|
231
|
+
new plugin(this.container, this)
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
this._PluginsManager.set(
|
|
236
|
+
plugins.name || plugins.constructor.name,
|
|
237
|
+
new plugins(this.container, this)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
_handleWindowEvent() {
|
|
243
|
+
// ==================== 事件监听 ====================
|
|
244
|
+
|
|
245
|
+
window.addEventListener("mousemove", (event) => {
|
|
246
|
+
const mouse = new THREE.Vector2();
|
|
247
|
+
// 计算鼠标在标准化设备坐标中的位置 (-1 到 +1)
|
|
248
|
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
249
|
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
250
|
+
|
|
251
|
+
// 更新射线
|
|
252
|
+
this.raycaster.setFromCamera(mouse, this.camera);
|
|
253
|
+
|
|
254
|
+
// 漫游模式:显示地面圆圈
|
|
255
|
+
if (this._CameraConfig.isRoamMode) {
|
|
256
|
+
// 只检测地面网格
|
|
257
|
+
const groundIntersects = this.raycaster.intersectObjects(
|
|
258
|
+
this._GroundPlane,
|
|
259
|
+
false
|
|
260
|
+
);
|
|
261
|
+
if (groundIntersects.length > 0) {
|
|
262
|
+
const point = groundIntersects[0].point;
|
|
263
|
+
this._RoamConfig.groundCircle.position.set(
|
|
264
|
+
point.x,
|
|
265
|
+
point.y + 0.01,
|
|
266
|
+
point.z
|
|
267
|
+
); // 稍微抬高避免z-fighting
|
|
268
|
+
this._RoamConfig.groundCircle.visible = true;
|
|
269
|
+
this.renderer.domElement.style.cursor = "pointer";
|
|
270
|
+
} else {
|
|
271
|
+
this._RoamConfig.groundCircle.visible = false;
|
|
272
|
+
this.renderer.domElement.style.cursor = "default";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 处理鼠标拖拽旋转视角
|
|
276
|
+
if (this._KeyboardEventHandler.isDragging) {
|
|
277
|
+
const deltaX =
|
|
278
|
+
event.clientX - this._KeyboardEventHandler.previousMousePosition.x;
|
|
279
|
+
const deltaY =
|
|
280
|
+
event.clientY - this._KeyboardEventHandler.previousMousePosition.y;
|
|
281
|
+
|
|
282
|
+
if (Math.abs(deltaX) > 0 || Math.abs(deltaY) > 0) {
|
|
283
|
+
// 水平旋转(左右拖拽)
|
|
284
|
+
const rotationY = deltaX * this._RoamConfig.rotateSpeed;
|
|
285
|
+
const rotationX = deltaY * this._RoamConfig.rotateSpeed;
|
|
286
|
+
|
|
287
|
+
// 获取相机的当前方向
|
|
288
|
+
const direction = new THREE.Vector3();
|
|
289
|
+
this.camera.getWorldDirection(direction);
|
|
290
|
+
|
|
291
|
+
// 创建旋转轴(垂直轴用于水平旋转)
|
|
292
|
+
const verticalAxis = new THREE.Vector3(0, 1, 0);
|
|
293
|
+
const horizontalAxis = new THREE.Vector3();
|
|
294
|
+
horizontalAxis.crossVectors(direction, verticalAxis).normalize();
|
|
295
|
+
|
|
296
|
+
// 应用水平旋转
|
|
297
|
+
direction.applyAxisAngle(verticalAxis, rotationY);
|
|
298
|
+
direction.applyAxisAngle(horizontalAxis, -rotationX);
|
|
299
|
+
|
|
300
|
+
// 限制垂直角度(防止翻转)
|
|
301
|
+
const currentAngle = Math.asin(direction.y);
|
|
302
|
+
const maxAngle = Math.PI / 2 - 0.1; // 限制在接近90度
|
|
303
|
+
if (currentAngle > maxAngle) direction.y = Math.sin(maxAngle);
|
|
304
|
+
if (currentAngle < -maxAngle) direction.y = Math.sin(-maxAngle);
|
|
305
|
+
direction.normalize();
|
|
306
|
+
|
|
307
|
+
// 更新相机朝向
|
|
308
|
+
const target = this.camera.position
|
|
309
|
+
.clone()
|
|
310
|
+
.add(direction.multiplyScalar(10));
|
|
311
|
+
this.camera.lookAt(target);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 更新上一次鼠标位置(在拖拽时)
|
|
315
|
+
this._KeyboardEventHandler.previousMousePosition.x = event.clientX;
|
|
316
|
+
this._KeyboardEventHandler.previousMousePosition.y = event.clientY;
|
|
317
|
+
} else {
|
|
318
|
+
// 非拖拽时也更新,以便下次拖拽时能正确计算
|
|
319
|
+
this._KeyboardEventHandler.previousMousePosition.x = event.clientX;
|
|
320
|
+
this._KeyboardEventHandler.previousMousePosition.y = event.clientY;
|
|
321
|
+
}
|
|
322
|
+
return; // 漫游模式下不处理其他悬停效果
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
window.addEventListener("dblclick", (event) => {
|
|
327
|
+
const mouse = new THREE.Vector2();
|
|
328
|
+
// 计算鼠标位置
|
|
329
|
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
330
|
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
331
|
+
|
|
332
|
+
// 更新射线
|
|
333
|
+
this.raycaster.setFromCamera(mouse, this.camera);
|
|
334
|
+
|
|
335
|
+
// 漫游模式:点击地面跳转视角
|
|
336
|
+
if (this._CameraConfig.isRoamMode) {
|
|
337
|
+
const groundIntersects = this.raycaster.intersectObjects(
|
|
338
|
+
this._GroundPlane,
|
|
339
|
+
false
|
|
340
|
+
);
|
|
341
|
+
if (groundIntersects.length > 0) {
|
|
342
|
+
// 如果正在动画中,取消之前的动画
|
|
343
|
+
if (
|
|
344
|
+
this._CameraConfig.isCameraAnimating &&
|
|
345
|
+
this._CameraConfig.cameraAnimationId !== null
|
|
346
|
+
) {
|
|
347
|
+
window.cancelAnimationFrame(this._CameraConfig.cameraAnimationId);
|
|
348
|
+
this._CameraConfig.isCameraAnimating = false;
|
|
349
|
+
this._CameraConfig.cameraAnimationId = null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const point = groundIntersects[0].point;
|
|
353
|
+
|
|
354
|
+
// 保存当前相机位置和朝向
|
|
355
|
+
const startPosition = this.camera.position.clone();
|
|
356
|
+
const currentDirection = new THREE.Vector3();
|
|
357
|
+
this.camera.getWorldDirection(currentDirection);
|
|
358
|
+
|
|
359
|
+
// 计算目标位置
|
|
360
|
+
const targetPosition = new THREE.Vector3(
|
|
361
|
+
point.x,
|
|
362
|
+
point.y + this._RoamConfig.cameraHeight,
|
|
363
|
+
point.z
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// 计算目标朝向(保持视角朝向不变)
|
|
367
|
+
const targetLookAt = targetPosition
|
|
368
|
+
.clone()
|
|
369
|
+
.add(currentDirection.multiplyScalar(10));
|
|
370
|
+
this._animateCameraToPosition(
|
|
371
|
+
startPosition,
|
|
372
|
+
targetPosition,
|
|
373
|
+
targetLookAt,
|
|
374
|
+
currentDirection
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
console.log("✅ 视角跳转到:", point);
|
|
378
|
+
console.log(" 起始位置:", startPosition);
|
|
379
|
+
console.log(" 目标位置:", targetPosition);
|
|
380
|
+
console.log(" 视角朝向:", currentDirection);
|
|
381
|
+
}
|
|
382
|
+
return; // 漫游模式下不处理其他点击事件
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
window.addEventListener("mousedown", (event) => {
|
|
387
|
+
if (this._CameraConfig.isRoamMode && event.button === 0) {
|
|
388
|
+
// 左键按下,开始拖拽
|
|
389
|
+
this._KeyboardEventHandler.isDragging = true;
|
|
390
|
+
this._KeyboardEventHandler.previousMousePosition.x = event.clientX;
|
|
391
|
+
this._KeyboardEventHandler.previousMousePosition.y = event.clientY;
|
|
392
|
+
this.renderer.domElement.style.cursor = "grabbing";
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
window.addEventListener("mouseup", (event) => {
|
|
397
|
+
if (this._CameraConfig.isRoamMode && event.button == 0) {
|
|
398
|
+
// 左键抬起,结束拖拽
|
|
399
|
+
this._KeyboardEventHandler.isDragging = false;
|
|
400
|
+
this.renderer.domElement.style.cursor = "default";
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
window.addEventListener("keydown", (event) => {
|
|
405
|
+
// 只在漫游模式下处理移动键
|
|
406
|
+
if (
|
|
407
|
+
this._CameraConfig.isRoamMode &&
|
|
408
|
+
this._KeyboardEventHandler.hasOwnProperty(event.key) &&
|
|
409
|
+
!this._CameraConfig.isCameraAnimating
|
|
410
|
+
) {
|
|
411
|
+
this._KeyboardEventHandler[event.key] = true;
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
window.addEventListener("keyup", (event) => {
|
|
415
|
+
// 只在漫游模式下处理移动键
|
|
416
|
+
if (
|
|
417
|
+
this._CameraConfig.isRoamMode &&
|
|
418
|
+
this._KeyboardEventHandler.hasOwnProperty(event.key) &&
|
|
419
|
+
!this._CameraConfig.isCameraAnimating
|
|
420
|
+
) {
|
|
421
|
+
this._KeyboardEventHandler[event.key] = false;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* 场景帧率渲染函数
|
|
428
|
+
*/
|
|
429
|
+
_animate() {
|
|
430
|
+
requestAnimationFrame(this._animate.bind(this));
|
|
431
|
+
|
|
432
|
+
// 如果启用了围绕自身旋转模式
|
|
433
|
+
if (
|
|
434
|
+
this._CameraConfig.rotateAroundSelf &&
|
|
435
|
+
this._CameraConfig.fixedCameraPosition
|
|
436
|
+
) {
|
|
437
|
+
// 先更新控制器
|
|
438
|
+
this.controls.update();
|
|
439
|
+
|
|
440
|
+
// 强制重置相机位置到固定位置(这样相机只能旋转,不能移动)
|
|
441
|
+
this.camera.position.copy(this._CameraConfig.fixedCameraPosition);
|
|
442
|
+
|
|
443
|
+
// 获取相机当前朝向,将target设置为相机前方一个固定距离的位置
|
|
444
|
+
const direction = new THREE.Vector3();
|
|
445
|
+
this.camera.getWorldDirection(direction);
|
|
446
|
+
this.controls.target
|
|
447
|
+
.copy(this.camera.position)
|
|
448
|
+
.add(direction.multiplyScalar(0.001));
|
|
449
|
+
|
|
450
|
+
// 再次更新控制器以确保正确
|
|
451
|
+
this.controls.update();
|
|
452
|
+
} else if (this._CameraConfig.isRoamMode) {
|
|
453
|
+
// 更新漫游相机
|
|
454
|
+
|
|
455
|
+
const direction = new THREE.Vector3();
|
|
456
|
+
this.camera.getWorldDirection(direction);
|
|
457
|
+
direction.y = 0; // 只在水平面移动
|
|
458
|
+
direction.normalize();
|
|
459
|
+
|
|
460
|
+
// 计算右方向(用于左右移动)
|
|
461
|
+
const right = new THREE.Vector3();
|
|
462
|
+
right.crossVectors(direction, new THREE.Vector3(0, 1, 0)).normalize();
|
|
463
|
+
|
|
464
|
+
let moveVector = new THREE.Vector3(0, 0, 0);
|
|
465
|
+
|
|
466
|
+
// 前后移动(W/S 或 上/下箭头)
|
|
467
|
+
if (this._KeyboardEventHandler.w || this._KeyboardEventHandler.ArrowUp) {
|
|
468
|
+
moveVector.add(direction);
|
|
469
|
+
}
|
|
470
|
+
if (
|
|
471
|
+
this._KeyboardEventHandler.s ||
|
|
472
|
+
this._KeyboardEventHandler.ArrowDown
|
|
473
|
+
) {
|
|
474
|
+
moveVector.sub(direction);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 左右移动(A/D 或 左/右箭头)
|
|
478
|
+
if (
|
|
479
|
+
this._KeyboardEventHandler.a ||
|
|
480
|
+
this._KeyboardEventHandler.ArrowLeft
|
|
481
|
+
) {
|
|
482
|
+
moveVector.sub(right);
|
|
483
|
+
}
|
|
484
|
+
if (
|
|
485
|
+
this._KeyboardEventHandler.d ||
|
|
486
|
+
this._KeyboardEventHandler.ArrowRight
|
|
487
|
+
) {
|
|
488
|
+
moveVector.add(right);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// 应用移动
|
|
492
|
+
if (moveVector.length() > 0) {
|
|
493
|
+
moveVector.normalize().multiplyScalar(this._RoamConfig.moveSpeed);
|
|
494
|
+
this.camera.position.add(moveVector);
|
|
495
|
+
|
|
496
|
+
// 保持相机高度
|
|
497
|
+
if (this.camera.position.y < this._RoamConfig.cameraHeight) {
|
|
498
|
+
this.camera.position.y = this._RoamConfig.cameraHeight;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// 更新相机朝向(保持看向相同方向)
|
|
502
|
+
const target = this.camera.position
|
|
503
|
+
.clone()
|
|
504
|
+
.add(direction.multiplyScalar(10));
|
|
505
|
+
this.camera.lookAt(target);
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
// 更新 OrbitControls(当启用阻尼时必须调用)
|
|
509
|
+
this.controls.update();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// 先渲染 3D 场景,再渲染标签(顺序很重要)
|
|
513
|
+
this.renderer.render(this.scene, this.camera);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 创建全景球体场景
|
|
518
|
+
* @param {*} texture
|
|
519
|
+
* @returns
|
|
520
|
+
*/
|
|
521
|
+
createPanoramaSphere(texture) {
|
|
522
|
+
if (!texture) return;
|
|
523
|
+
|
|
524
|
+
// 移除旧的球体
|
|
525
|
+
if (this._PanoramaSphere) {
|
|
526
|
+
this.scene.remove(this._PanoramaSphere);
|
|
527
|
+
this._PanoramaSphere.geometry.dispose();
|
|
528
|
+
this._PanoramaSphere.material.dispose();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
//-------------------- 方法1:使用 EquirectangularReflectionMapping + 翻转X轴
|
|
532
|
+
|
|
533
|
+
console.log(
|
|
534
|
+
"🔧 使用方法1: EquirectangularReflectionMapping + scale(1,-1,-1)"
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
538
|
+
texture.minFilter = THREE.LinearFilter;
|
|
539
|
+
texture.magFilter = THREE.LinearFilter;
|
|
540
|
+
texture.flipY = false;
|
|
541
|
+
|
|
542
|
+
const geometry = new THREE.SphereGeometry(1000, 64, 32);
|
|
543
|
+
geometry.scale(1, -1, -1); // 沿Y,Z轴翻转
|
|
544
|
+
|
|
545
|
+
const material = new THREE.MeshBasicMaterial({
|
|
546
|
+
map: texture,
|
|
547
|
+
side: THREE.BackSide,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
this._PanoramaSphere = new THREE.Mesh(geometry, material);
|
|
551
|
+
//-------------------------------------------------
|
|
552
|
+
|
|
553
|
+
//------------------- 方法2:使用默认UV映射 + 负半径
|
|
554
|
+
// console.log("🔧 使用方法2: 默认UV映射 + 负半径");
|
|
555
|
+
// texture.mapping = THREE.UVMapping; // 使用默认UV映射
|
|
556
|
+
// texture.minFilter = THREE.LinearFilter;
|
|
557
|
+
// texture.magFilter = THREE.LinearFilter;
|
|
558
|
+
// texture.flipY = false;
|
|
559
|
+
// // 使用负半径创建球体(Three.js会自动翻转)
|
|
560
|
+
// const geometry = new THREE.SphereGeometry(-1000, 64, 32);
|
|
561
|
+
|
|
562
|
+
// const material = new THREE.MeshBasicMaterial({
|
|
563
|
+
// map: texture,
|
|
564
|
+
// side: THREE.FrontSide, // 使用正面,因为几何体已经翻转
|
|
565
|
+
// });
|
|
566
|
+
// panoramaSphere = new THREE.Mesh(geometry, material);
|
|
567
|
+
//------------------------------------------------
|
|
568
|
+
|
|
569
|
+
this.scene.add(this._PanoramaSphere);
|
|
570
|
+
|
|
571
|
+
console.log("✅ 全景球体创建成功!");
|
|
572
|
+
console.log("球体位置:", this._PanoramaSphere.position);
|
|
573
|
+
console.log("相机位置:", this.camera.position);
|
|
574
|
+
console.log("材质配置:", {
|
|
575
|
+
hasTexture: !!this._PanoramaSphere.material.map,
|
|
576
|
+
side: this._PanoramaSphere.material.side,
|
|
577
|
+
mapping: texture.mapping,
|
|
578
|
+
textureSize: texture.image
|
|
579
|
+
? `${texture.image.width}x${texture.image.height}`
|
|
580
|
+
: "未知",
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// 确保相机朝向正确
|
|
584
|
+
// this.camera.lookAt(0, 0, -1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* 进入第一人称模式
|
|
589
|
+
*/
|
|
590
|
+
enterFirstPersonMode() {
|
|
591
|
+
if (this._CameraConfig.isRoamMode) {
|
|
592
|
+
this.exitRoamMode();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 动画完成后,启用围绕自身旋转模式
|
|
596
|
+
// 保存相机固定位置
|
|
597
|
+
this._CameraConfig.fixedCameraPosition = this.camera.position.clone();
|
|
598
|
+
|
|
599
|
+
// 获取相机当前朝向,将target设置在相机前方
|
|
600
|
+
const direction = new THREE.Vector3();
|
|
601
|
+
this.camera.getWorldDirection(direction);
|
|
602
|
+
this.controls.target
|
|
603
|
+
.copy(this.camera.position)
|
|
604
|
+
.add(direction.multiplyScalar(1));
|
|
605
|
+
this.controls.update();
|
|
606
|
+
|
|
607
|
+
// 启用围绕自身旋转模式(在动画循环中会持续同步target并限制位置)
|
|
608
|
+
this._CameraConfig.rotateAroundSelf = true;
|
|
609
|
+
|
|
610
|
+
// 禁用平移功能,只允许旋转(围绕自身)
|
|
611
|
+
this.controls.enablePan = false; // 禁用平移
|
|
612
|
+
|
|
613
|
+
// 保持缩放功能,但限制距离范围(让相机位置尽量固定)
|
|
614
|
+
this.controls.minDistance = 0.001;
|
|
615
|
+
this.controls.maxDistance = 0.001;
|
|
616
|
+
|
|
617
|
+
// 允许用户从各个角度查看(移除垂直角度限制)
|
|
618
|
+
this.controls.maxPolarAngle = Math.PI; // 允许从下方查看
|
|
619
|
+
console.log(
|
|
620
|
+
` 相机位置: (${this.camera.position.x.toFixed(
|
|
621
|
+
2
|
|
622
|
+
)}, ${this.camera.position.y.toFixed(
|
|
623
|
+
2
|
|
624
|
+
)}, ${this.camera.position.z.toFixed(2)})`
|
|
625
|
+
);
|
|
626
|
+
console.log(
|
|
627
|
+
` 控制器目标: (${this.controls.target.x.toFixed(
|
|
628
|
+
2
|
|
629
|
+
)}, ${this.controls.target.y.toFixed(
|
|
630
|
+
2
|
|
631
|
+
)}, ${this.controls.target.z.toFixed(2)})`
|
|
632
|
+
);
|
|
633
|
+
console.log(`💡 现在可以用鼠标拖拽围绕相机自身旋转查看四周,滚轮缩放`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* 退出第一人称模式
|
|
638
|
+
*/
|
|
639
|
+
exitFirstPersonMode() {
|
|
640
|
+
if (!this.controls || !this._OrbitControlsConfig) {
|
|
641
|
+
console.error("❌ 控制器或原始配置未初始化");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
console.log("🔄 退出第一人称模式,恢复原始控制状态");
|
|
646
|
+
|
|
647
|
+
// 禁用围绕自身旋转模式
|
|
648
|
+
this._CameraConfig.rotateAroundSelf = false;
|
|
649
|
+
this._CameraConfig.fixedCameraPosition = null;
|
|
650
|
+
|
|
651
|
+
// 恢复 OrbitControls 的原始配置
|
|
652
|
+
this.controls.enablePan = this._OrbitControlsConfig.enablePan;
|
|
653
|
+
this.controls.enableZoom = this._OrbitControlsConfig.enableZoom;
|
|
654
|
+
this.controls.minDistance = this._OrbitControlsConfig.minDistance;
|
|
655
|
+
this.controls.maxDistance = this._OrbitControlsConfig.maxDistance;
|
|
656
|
+
this.controls.maxPolarAngle = this._OrbitControlsConfig.maxPolarAngle;
|
|
657
|
+
this.controls.target.copy(this._OrbitControlsConfig.target);
|
|
658
|
+
this.controls.update();
|
|
659
|
+
|
|
660
|
+
console.log("✅ 已退出第一人称模式,现在可以自由移动和旋转相机");
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 进入漫游模式
|
|
665
|
+
*/
|
|
666
|
+
enterRoamMode() {
|
|
667
|
+
// 如果第一人称视角已启用,先退出
|
|
668
|
+
if (this._CameraConfig.rotateAroundSelf) {
|
|
669
|
+
this.exitFirstPersonMode();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (!this._RoamConfig.groundCircle) {
|
|
673
|
+
// 创建地面圆圈(用于漫游模式 的地面圆圈指示器)
|
|
674
|
+
const circleGeometry = new THREE.RingGeometry(0.3, 0.5, 32);
|
|
675
|
+
const circleMaterial = new THREE.MeshBasicMaterial({
|
|
676
|
+
color: 0x00ffff,
|
|
677
|
+
side: THREE.DoubleSide,
|
|
678
|
+
transparent: true,
|
|
679
|
+
opacity: 0.6,
|
|
680
|
+
});
|
|
681
|
+
this._RoamConfig.groundCircle = new THREE.Mesh(
|
|
682
|
+
circleGeometry,
|
|
683
|
+
circleMaterial
|
|
684
|
+
);
|
|
685
|
+
this._RoamConfig.groundCircle.rotation.x = -Math.PI / 2; // 旋转使其水平
|
|
686
|
+
this._RoamConfig.groundCircle.visible = false; // 默认隐藏
|
|
687
|
+
this.scene.add(this._RoamConfig.groundCircle);
|
|
688
|
+
console.log("✅ 地面圆圈指示器创建成功");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
this._CameraConfig.isRoamMode = true;
|
|
692
|
+
|
|
693
|
+
// 启用漫游模式
|
|
694
|
+
this.controls.enabled = false; // 禁用轨道控制器
|
|
695
|
+
|
|
696
|
+
this._RoamConfig.groundCircle.visible = false; // 初始隐藏圆圈
|
|
697
|
+
|
|
698
|
+
// 如果相机位置太低,调整到合适高度
|
|
699
|
+
if (this.camera.position.y < this._RoamConfig.cameraHeight) {
|
|
700
|
+
this.camera.position.y = this._RoamConfig.cameraHeight;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
console.log("✅ 进入视角漫游模式");
|
|
704
|
+
console.log("📍 当前相机位置:", this.camera.position);
|
|
705
|
+
console.log("⌨️ 操作说明:");
|
|
706
|
+
console.log(" • 鼠标悬停地面显示圆圈");
|
|
707
|
+
console.log(" • 点击地面跳转视角");
|
|
708
|
+
console.log(" • WASD/方向键移动");
|
|
709
|
+
console.log(" • 鼠标拖拽改变朝向");
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
exitRoamMode() {
|
|
713
|
+
// 退出漫游模式
|
|
714
|
+
this.controls.enabled = true;
|
|
715
|
+
this._RoamConfig.groundCircle.visible = false;
|
|
716
|
+
this._CameraConfig.isRoamMode = false;
|
|
717
|
+
console.log("✅ 退出视角漫游模式");
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* 创建3d场景组
|
|
722
|
+
* @param {string} name - 场景组名称
|
|
723
|
+
* @returns {THREE.Group} - 返回创建的3d场景组
|
|
724
|
+
*/
|
|
725
|
+
createThreeGroup(name) {
|
|
726
|
+
if (this._ThreeGroupMap.has(name)) {
|
|
727
|
+
return this._ThreeGroupMap.get(name);
|
|
728
|
+
}
|
|
729
|
+
const threeGroup = new THREE.Group();
|
|
730
|
+
this.scene.add(threeGroup);
|
|
731
|
+
this._ThreeGroupMap.set(name, threeGroup);
|
|
732
|
+
return threeGroup;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* 将相机平滑移动到指定位置,并可以围绕位置旋转查看
|
|
737
|
+
* @param {THREE.Vector3} position - 标签的3D位置
|
|
738
|
+
* @param {string} contentHtml - 标签显示的HTML内容
|
|
739
|
+
* @param {Object} options - 可选配置项
|
|
740
|
+
* @param {number} options.heightOffset - 相机在标签下方的距离,默认50
|
|
741
|
+
* @param {number} options.distance - 相机与标签的水平距离,默认100
|
|
742
|
+
* @param {number} options.duration - 动画时长(毫秒),默认1500
|
|
743
|
+
*/
|
|
744
|
+
flyTo(viewInfo = {}, options = {}) {
|
|
745
|
+
return new Promise((resolve, reject) => {
|
|
746
|
+
if (!this.camera || !this.controls) {
|
|
747
|
+
reject("❌ 相机或控制器未初始化");
|
|
748
|
+
console.error("❌ 相机或控制器未初始化");
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
const { position, target } = viewInfo;
|
|
752
|
+
if (!position || !target) {
|
|
753
|
+
console.error("❌ 视图信息格式错误,需要包含 position 和 target");
|
|
754
|
+
reject("❌ 视图信息格式错误,需要包含 position 和 target");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const { x: positionX = 0, y: positionY = 0, z: positionZ = 0 } = position;
|
|
758
|
+
const { x: targetX = 0, y: targetY = 0, z: targetZ = 0 } = target;
|
|
759
|
+
|
|
760
|
+
const {
|
|
761
|
+
heightOffset = 50, // 相机在标签下方的距离
|
|
762
|
+
distance = 0, // 相机与标签的水平距离
|
|
763
|
+
duration = 1500, // 动画时长(毫秒)
|
|
764
|
+
} = options;
|
|
765
|
+
|
|
766
|
+
const targetPosition = new THREE.Vector3(positionX, positionY, positionZ);
|
|
767
|
+
|
|
768
|
+
const targetLookAt = new THREE.Vector3(targetX, targetY, targetZ);
|
|
769
|
+
|
|
770
|
+
// 保存初始位置和目标
|
|
771
|
+
const startPosition = this.camera.position.clone();
|
|
772
|
+
const startTarget = this.controls.target.clone();
|
|
773
|
+
|
|
774
|
+
// 动画参数
|
|
775
|
+
let startTime = null;
|
|
776
|
+
|
|
777
|
+
// 缓动函数(ease-in-out)
|
|
778
|
+
function easeInOutCubic(t) {
|
|
779
|
+
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// 动画函数
|
|
783
|
+
function animateCamera(currentTime) {
|
|
784
|
+
if (!startTime) startTime = currentTime;
|
|
785
|
+
const elapsed = currentTime - startTime;
|
|
786
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
787
|
+
const easedProgress = easeInOutCubic(progress);
|
|
788
|
+
|
|
789
|
+
// 插值计算当前位置和目标
|
|
790
|
+
this.camera.position.lerpVectors(
|
|
791
|
+
startPosition,
|
|
792
|
+
targetPosition,
|
|
793
|
+
easedProgress
|
|
794
|
+
);
|
|
795
|
+
this.controls.target.lerpVectors(
|
|
796
|
+
startTarget,
|
|
797
|
+
targetLookAt,
|
|
798
|
+
easedProgress
|
|
799
|
+
);
|
|
800
|
+
this.controls.update();
|
|
801
|
+
|
|
802
|
+
// 如果动画未完成,继续下一帧
|
|
803
|
+
if (progress < 1) {
|
|
804
|
+
requestAnimationFrame(animateCamera.bind(this));
|
|
805
|
+
} else {
|
|
806
|
+
resolve();
|
|
807
|
+
console.log("✅ 摄像头已移动到指定位置");
|
|
808
|
+
console.log(
|
|
809
|
+
` 位置: (${targetPosition.x.toFixed(
|
|
810
|
+
2
|
|
811
|
+
)}, ${targetPosition.y.toFixed(2)}, ${targetPosition.z.toFixed(2)})`
|
|
812
|
+
);
|
|
813
|
+
console.log(
|
|
814
|
+
` 目标: (${targetLookAt.x.toFixed(2)}, ${targetLookAt.y.toFixed(
|
|
815
|
+
2
|
|
816
|
+
)}, ${targetLookAt.z.toFixed(2)})`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// 开始动画
|
|
822
|
+
requestAnimationFrame(animateCamera.bind(this));
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
_getCameraInfo() {
|
|
827
|
+
if (!this.camera || !this.controls) return;
|
|
828
|
+
|
|
829
|
+
const pos = this.camera.position;
|
|
830
|
+
const target = this.controls.target;
|
|
831
|
+
const distance = pos.distanceTo(target);
|
|
832
|
+
return {
|
|
833
|
+
position: {
|
|
834
|
+
x: parseFloat(pos.x.toFixed(2)),
|
|
835
|
+
y: parseFloat(pos.y.toFixed(2)),
|
|
836
|
+
z: parseFloat(pos.z.toFixed(2)),
|
|
837
|
+
},
|
|
838
|
+
target: {
|
|
839
|
+
x: parseFloat(target.x.toFixed(2)),
|
|
840
|
+
y: parseFloat(target.y.toFixed(2)),
|
|
841
|
+
z: parseFloat(target.z.toFixed(2)),
|
|
842
|
+
},
|
|
843
|
+
distance: parseFloat(distance.toFixed(2)),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* 相机平滑过渡动画
|
|
849
|
+
* @param {THREE.Camera} camera - 相机
|
|
850
|
+
* @param {THREE.Vector3} startPos - 起始位置
|
|
851
|
+
* @param {THREE.Vector3} targetPos - 目标位置
|
|
852
|
+
* @param {THREE.Vector3} targetLookAt - 目标朝向
|
|
853
|
+
* @param {THREE.Vector3} direction - 方向
|
|
854
|
+
*/
|
|
855
|
+
_animateCameraToPosition(startPos, targetPos, targetLookAt, direction) {
|
|
856
|
+
this._CameraConfig.isCameraAnimating = true;
|
|
857
|
+
|
|
858
|
+
const duration = 1000; // 动画持续时间(毫秒)
|
|
859
|
+
const startTime = performance.now();
|
|
860
|
+
|
|
861
|
+
// 缓动函数:easeInOutCubic(平滑的加速和减速)
|
|
862
|
+
function easeInOutCubic(t) {
|
|
863
|
+
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const animate = (currentTime) => {
|
|
867
|
+
const elapsed = currentTime - startTime;
|
|
868
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
869
|
+
const easedProgress = easeInOutCubic(progress);
|
|
870
|
+
|
|
871
|
+
// 使用 lerp 插值计算当前位置
|
|
872
|
+
this.camera.position.lerpVectors(startPos, targetPos, easedProgress);
|
|
873
|
+
|
|
874
|
+
// 保持视角朝向不变(看向相同方向)
|
|
875
|
+
const currentLookAt = this.camera.position
|
|
876
|
+
.clone()
|
|
877
|
+
.add(direction.multiplyScalar(10));
|
|
878
|
+
this.camera.lookAt(currentLookAt);
|
|
879
|
+
|
|
880
|
+
// 如果动画未完成,继续下一帧
|
|
881
|
+
if (progress < 1) {
|
|
882
|
+
this._CameraConfig.cameraAnimationId = requestAnimationFrame(animate);
|
|
883
|
+
} else {
|
|
884
|
+
// 动画完成,确保最终位置精确
|
|
885
|
+
this.camera.position.copy(targetPos);
|
|
886
|
+
this.camera.lookAt(targetLookAt);
|
|
887
|
+
this._CameraConfig.isCameraAnimating = false;
|
|
888
|
+
this._CameraConfig.cameraAnimationId = null;
|
|
889
|
+
console.log("✅ 视角平滑跳转完成");
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// 开始动画
|
|
894
|
+
this._CameraConfig.cameraAnimationId = requestAnimationFrame(animate);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export default ThreeSdk;
|