three-player-controller 0.3.3 → 0.3.5

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/dist/index.mjs CHANGED
@@ -1,31 +1,308 @@
1
1
  // src/playerController.ts
2
- import * as THREE from "three";
2
+ import * as THREE3 from "three";
3
3
  import { acceleratedRaycast, MeshBVH, MeshBVHHelper } from "three-mesh-bvh";
4
4
  import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js";
5
5
  import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
6
6
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
7
7
  import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
8
8
 
9
+ // assets/imgs/break.png
10
+ var break_default = "data:image/png;base64,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";
11
+
9
12
  // assets/imgs/fly.png
10
13
  var fly_default = "data:image/png;base64,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";
11
14
 
12
15
  // assets/imgs/jump.png
13
16
  var jump_default = "data:image/png;base64,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";
14
17
 
18
+ // assets/imgs/vehicle.png
19
+ var vehicle_default = "data:image/png;base64,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";
20
+
15
21
  // assets/imgs/view.png
16
22
  var view_default = "data:image/png;base64,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";
17
23
 
24
+ // src/utils/pathPlanner.ts
25
+ import * as THREE from "three";
26
+ var PathNode = class {
27
+ constructor(position) {
28
+ this.g = Infinity;
29
+ // 实际代价
30
+ this.h = 0;
31
+ // 估计代价
32
+ this.f = Infinity;
33
+ // f = g + h
34
+ this.parent = null;
35
+ this.position = position.clone();
36
+ }
37
+ equals(other) {
38
+ return this.position.distanceTo(other.position) < 0.01;
39
+ }
40
+ };
41
+ var PriorityQueue = class {
42
+ constructor() {
43
+ this.elements = [];
44
+ }
45
+ enqueue(item, priority) {
46
+ this.elements.push({ priority, item });
47
+ this.elements.sort((a, b) => a.priority - b.priority);
48
+ }
49
+ dequeue() {
50
+ return this.elements.shift()?.item;
51
+ }
52
+ isEmpty() {
53
+ return this.elements.length === 0;
54
+ }
55
+ contains(item, compareFn) {
56
+ return this.elements.some((e) => compareFn(e.item, item));
57
+ }
58
+ update(item, newPriority, compareFn) {
59
+ const index = this.elements.findIndex((e) => compareFn(e.item, item));
60
+ if (index !== -1) {
61
+ this.elements[index].priority = newPriority;
62
+ this.elements.sort((a, b) => a.priority - b.priority);
63
+ }
64
+ }
65
+ };
66
+ var PathPlanner = class {
67
+ constructor(obstacleChecker, config = {}) {
68
+ this.debugLines = [];
69
+ this.debugPoints = [];
70
+ this.obstacleChecker = obstacleChecker;
71
+ this.config = {
72
+ debugEnabled: false,
73
+ scale: 1,
74
+ ...config
75
+ };
76
+ }
77
+ // 计算启发式距离
78
+ heuristic(a, b) {
79
+ return a.distanceTo(b);
80
+ }
81
+ // A*路径规划算法
82
+ findPath(start, goal) {
83
+ const startTime = performance.now();
84
+ if (!this.obstacleChecker.isBlocked(start, goal)) {
85
+ return [goal];
86
+ }
87
+ const navigationPoints = this.obstacleChecker.getNavigationNodes(start, goal);
88
+ const allNodes = [new PathNode(start), new PathNode(goal), ...navigationPoints.map((p) => new PathNode(p))];
89
+ if (allNodes.length < 2) {
90
+ console.warn("\u5BFC\u822A\u8282\u70B9\u4E0D\u8DB3\uFF0C\u8FD4\u56DE\u76F4\u7EBF\u8DEF\u5F84");
91
+ return [goal];
92
+ }
93
+ const startNode = allNodes[0];
94
+ const goalNode = allNodes[1];
95
+ startNode.g = 0;
96
+ startNode.h = this.heuristic(startNode.position, goalNode.position);
97
+ startNode.f = startNode.h;
98
+ const openList = new PriorityQueue();
99
+ const closedSet = /* @__PURE__ */ new Set();
100
+ openList.enqueue(startNode, startNode.f);
101
+ const nodeEquals = (a, b) => a.equals(b);
102
+ while (!openList.isEmpty()) {
103
+ const current = openList.dequeue();
104
+ if (!current) break;
105
+ if (current.equals(goalNode)) {
106
+ const path = this.reconstructPath(current);
107
+ const endTime = performance.now();
108
+ if (this.config.debugEnabled) {
109
+ this.visualizePath([start, ...path]);
110
+ }
111
+ return path;
112
+ }
113
+ closedSet.add(current);
114
+ for (const neighbor of allNodes) {
115
+ if (closedSet.has(neighbor)) continue;
116
+ if (this.obstacleChecker.isBlocked(current.position, neighbor.position)) {
117
+ continue;
118
+ }
119
+ const tentativeG = current.g + current.position.distanceTo(neighbor.position);
120
+ if (tentativeG < neighbor.g) {
121
+ neighbor.parent = current;
122
+ neighbor.g = tentativeG;
123
+ neighbor.h = this.heuristic(neighbor.position, goalNode.position);
124
+ neighbor.f = neighbor.g + neighbor.h;
125
+ if (openList.contains(neighbor, nodeEquals)) {
126
+ openList.update(neighbor, neighbor.f, nodeEquals);
127
+ } else {
128
+ openList.enqueue(neighbor, neighbor.f);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ console.warn("A*\u672A\u627E\u5230\u8DEF\u5F84\uFF0C\u4F7F\u7528\u76F4\u7EBF\u8DEF\u5F84");
134
+ return [goal];
135
+ }
136
+ /**
137
+ * 重建路径
138
+ */
139
+ reconstructPath(endNode) {
140
+ const path = [];
141
+ let current = endNode;
142
+ while (current !== null) {
143
+ path.unshift(current.position.clone());
144
+ current = current.parent;
145
+ }
146
+ if (path.length > 0) {
147
+ path.shift();
148
+ }
149
+ return this.smoothPath(path);
150
+ }
151
+ /**
152
+ * 路径平滑
153
+ */
154
+ smoothPath(path) {
155
+ if (path.length <= 2) return path;
156
+ const smoothed = [path[0]];
157
+ let current = 0;
158
+ while (current < path.length - 1) {
159
+ let farthest = current + 1;
160
+ for (let i = path.length - 1; i > current + 1; i--) {
161
+ if (!this.obstacleChecker.isBlocked(path[current], path[i])) {
162
+ farthest = i;
163
+ break;
164
+ }
165
+ }
166
+ smoothed.push(path[farthest]);
167
+ current = farthest;
168
+ }
169
+ return smoothed;
170
+ }
171
+ /**
172
+ * 可视化路径
173
+ */
174
+ visualizePath(path) {
175
+ if (!this.config.scene || !this.config.debugEnabled) return;
176
+ this.clearVisualization();
177
+ const scale = this.config.scale || 1;
178
+ if (path.length > 1) {
179
+ const points = path.map((p) => p.clone());
180
+ const geometry = new THREE.BufferGeometry().setFromPoints(points);
181
+ const material = new THREE.LineBasicMaterial({
182
+ color: 65280,
183
+ linewidth: 3
184
+ });
185
+ const line = new THREE.Line(geometry, material);
186
+ this.config.scene.add(line);
187
+ this.debugLines.push(line);
188
+ }
189
+ path.forEach((point, index) => {
190
+ const geometry = new THREE.SphereGeometry(20 * scale);
191
+ const material = new THREE.MeshBasicMaterial({
192
+ color: index === path.length - 1 ? 16711680 : 65280
193
+ });
194
+ const sphere = new THREE.Mesh(geometry, material);
195
+ sphere.position.copy(point);
196
+ this.config.scene.add(sphere);
197
+ this.debugPoints.push(sphere);
198
+ });
199
+ }
200
+ /**
201
+ * 清除路径可视化
202
+ */
203
+ clearVisualization() {
204
+ if (!this.config.scene) return;
205
+ this.debugLines.forEach((line) => {
206
+ this.config.scene.remove(line);
207
+ line.geometry.dispose();
208
+ line.material.dispose();
209
+ });
210
+ this.debugLines = [];
211
+ this.debugPoints.forEach((point) => {
212
+ this.config.scene.remove(point);
213
+ point.geometry.dispose();
214
+ point.material.dispose();
215
+ });
216
+ this.debugPoints = [];
217
+ }
218
+ /**
219
+ * 更新配置
220
+ */
221
+ updateConfig(config) {
222
+ this.config = { ...this.config, ...config };
223
+ }
224
+ /**
225
+ * 销毁
226
+ */
227
+ dispose() {
228
+ this.clearVisualization();
229
+ }
230
+ };
231
+
232
+ // src/utils/useVehicleController.ts
233
+ import * as THREE2 from "three";
234
+ function createVehicleController(world, chassisBody, wheels, wheelsInfo) {
235
+ if (!world || !chassisBody) return { vehicle: null, updateWheelVisuals: () => {
236
+ } };
237
+ const vehicle = world.createVehicleController(chassisBody);
238
+ const suspensionDirection = new THREE2.Vector3(0, -1, 0);
239
+ wheelsInfo.forEach((wheel, index) => {
240
+ vehicle.addWheel(wheel.position, suspensionDirection, wheel.axleCs, wheel.suspensionRestLength, wheel.radius);
241
+ vehicle.setWheelChassisConnectionPointCs(index, wheel.position);
242
+ vehicle.setWheelDirectionCs(index, suspensionDirection);
243
+ vehicle.setWheelAxleCs(index, wheel.axleCs);
244
+ vehicle.setWheelSuspensionRestLength(index, wheel.suspensionRestLength);
245
+ vehicle.setWheelRadius(index, wheel.radius);
246
+ vehicle.setWheelMaxSuspensionTravel(index, wheel.suspensionRestLength * 1);
247
+ vehicle.setWheelSuspensionStiffness(index, 250);
248
+ vehicle.setWheelSuspensionCompression(index, 6);
249
+ vehicle.setWheelSuspensionRelaxation(index, 6);
250
+ vehicle.setWheelMaxSuspensionForce(index, 1e4);
251
+ vehicle.setWheelBrake(index, 0);
252
+ vehicle.setWheelSteering(index, 0);
253
+ vehicle.setWheelEngineForce(index, 0);
254
+ vehicle.setWheelFrictionSlip(index, 20);
255
+ vehicle.setWheelSideFrictionStiffness(index, 2);
256
+ });
257
+ const up = new THREE2.Vector3(0, 1, 0);
258
+ const _wheelSteeringQuat = new THREE2.Quaternion();
259
+ const _wheelRotationQuat = new THREE2.Quaternion();
260
+ function updateWheelVisuals() {
261
+ for (const [index, wheelObj] of wheels.entries()) {
262
+ if (!wheelObj) continue;
263
+ try {
264
+ const wheelAxleCs = vehicle.wheelAxleCs(index) ?? new THREE2.Vector3(1, 0, 0);
265
+ const connection = vehicle.wheelChassisConnectionPointCs(index)?.y ?? 0;
266
+ const suspension = vehicle.wheelSuspensionLength(index) ?? 0;
267
+ const steering = vehicle.wheelSteering(index) ?? 0;
268
+ const rotationRad = vehicle.wheelRotation(index) ?? 0;
269
+ wheelObj.position.y = connection - suspension;
270
+ _wheelSteeringQuat.setFromAxisAngle(up, steering);
271
+ _wheelRotationQuat.setFromAxisAngle(wheelAxleCs, rotationRad);
272
+ wheelObj.quaternion.copy(_wheelSteeringQuat).multiply(_wheelRotationQuat);
273
+ } catch (e) {
274
+ }
275
+ }
276
+ }
277
+ function destroy() {
278
+ try {
279
+ world.removeVehicleController(vehicle);
280
+ } catch {
281
+ }
282
+ }
283
+ return {
284
+ vehicle,
285
+ updateWheelVisuals,
286
+ destroy
287
+ };
288
+ }
289
+
18
290
  // src/playerController.ts
19
- THREE.Mesh.prototype.raycast = acceleratedRaycast;
291
+ THREE3.Mesh.prototype.raycast = acceleratedRaycast;
20
292
  var controllerInstance = null;
21
- var clock = new THREE.Clock();
293
+ var clock = new THREE3.Clock();
22
294
  var PlayerController = class {
23
295
  constructor() {
24
296
  // ==================== 基本配置与参数 ====================
25
297
  this.loader = new GLTFLoader();
298
+ this.controllerMode = 0;
299
+ // 0: 人物 1: 车辆
300
+ this.enableOverShoulderView = false;
301
+ this.isChangeControllerTransitionTimer = null;
26
302
  // ==================== 玩家基本属性 ====================
27
303
  this.playerRadius = 45;
28
304
  this.playerHeight = 180;
305
+ // 玩家参考身高
29
306
  this.isFirstPerson = false;
30
307
  this.boundingBoxMinY = 0;
31
308
  // ==================== 测试参数 ====================
@@ -36,7 +313,66 @@ var PlayerController = class {
36
313
  this.collider = null;
37
314
  this.visualizer = null;
38
315
  this.person = null;
39
- this.vehicle = null;
316
+ this.personHead = null;
317
+ this.collected = [];
318
+ this.dynamicCollider = null;
319
+ this.dynamicCollected = [];
320
+ // ==================== 多车辆相关 ====================
321
+ this.vehicles = [];
322
+ // 所有已加载车辆
323
+ this.activeVehicle = null;
324
+ // 当前驾驶/交互的车辆
325
+ this.vehicleLength = 6;
326
+ // 车辆参考长度
327
+ this.wheelSteeringQuat = new THREE3.Quaternion();
328
+ this.wheelRotationQuat = new THREE3.Quaternion();
329
+ this.RAPIER = null;
330
+ this.world = null;
331
+ // 全局车辆共享参数
332
+ this.vehicleParams = {
333
+ debug: {
334
+ showPhysicsBox: true
335
+ },
336
+ chassis: {
337
+ linearDamping: 0.5,
338
+ angularDamping: 0.5
339
+ },
340
+ model: {
341
+ rotation: -Math.PI / 2
342
+ },
343
+ power: {
344
+ accelerateForce: 50,
345
+ // 推进
346
+ brakeForce: 200,
347
+ // 刹车
348
+ maxSpeed: 1e4
349
+ // 最大速度
350
+ },
351
+ steering: {
352
+ maxSteerAngle: Math.PI / 4,
353
+ steerSpeed: 0.5,
354
+ steerReturnSpeed: 1
355
+ },
356
+ followVehicleDirection: true
357
+ };
358
+ this.camBehindDir = new THREE3.Vector3(0, 0, 1);
359
+ // ==================== 上车相关 ====================
360
+ this.isMovingToBoardingPoint = false;
361
+ this.boardingWaypoints = [];
362
+ this.currentWaypointIndex = 0;
363
+ this.boardingTargetDir = null;
364
+ this.boardingMoveSpeed = 300;
365
+ this.boardingRotateSpeed = 10;
366
+ this.flip180Quat = new THREE3.Quaternion().setFromAxisAngle(
367
+ new THREE3.Vector3(0, 1, 0),
368
+ Math.PI
369
+ );
370
+ this.closeVehicleDoorTimer = null;
371
+ this.boardingPointWorld = null;
372
+ this.isBoardingAnimPlaying = false;
373
+ this.closeDoorTriggered = false;
374
+ this.isExitAnimPlaying = false;
375
+ this.closeExitDoorTriggered = false;
40
376
  // ==================== 状态开关 ====================
41
377
  this.playerIsOnGround = false;
42
378
  this.isupdate = true;
@@ -58,59 +394,92 @@ var PlayerController = class {
58
394
  this.jumpBtnEl = null;
59
395
  this.flyBtnEl = null;
60
396
  this.viewBtnEl = null;
397
+ this.vehicleBtnEl = null;
61
398
  this.lookPointerId = null;
62
399
  this.isLookDown = false;
63
400
  this.lastTouchX = 0;
64
401
  this.lastTouchY = 0;
402
+ this.nearCheckLocal = new THREE3.Vector3();
403
+ this.nearCheckWorld = new THREE3.Vector3();
404
+ this.isNearVehicle = false;
65
405
  // ==================== 第三人称相机参数 ====================
66
406
  this._camCollisionLerp = 0.18;
67
- // 平滑系数
68
407
  this._camEpsilon = 0.35;
69
- // 摄像机与障碍物之间的安全距离
70
- this._minCamDistance = 1;
71
- // 摄像机最小距离
72
- this._maxCamDistance = 4.4;
73
- // 摄像机最大距离
408
+ this.minCamDistance = 1;
409
+ this.maxCamDistance = 4.4;
74
410
  this.orginMaxCamDistance = 4.4;
75
411
  // ==================== 物理/运动 ====================
76
- this.playerVelocity = new THREE.Vector3();
77
- // 玩家速度向量
78
- this.upVector = new THREE.Vector3(0, 1, 0);
412
+ this.playerVelocity = new THREE3.Vector3();
413
+ this.upVector = new THREE3.Vector3(0, 1, 0);
79
414
  // ==================== 临时复用向量/矩阵 ====================
80
- this.tempVector = new THREE.Vector3();
81
- this.tempVector2 = new THREE.Vector3();
82
- this.tempBox = new THREE.Box3();
83
- this.tempMat = new THREE.Matrix4();
84
- this.tempSegment = new THREE.Line3();
415
+ this.tempVector = new THREE3.Vector3();
416
+ this.tempVector2 = new THREE3.Vector3();
417
+ this.tempBox = new THREE3.Box3();
418
+ this.tempMat = new THREE3.Matrix4();
419
+ this.tempSegment = new THREE3.Line3();
85
420
  this.recheckAnimTimer = null;
86
421
  // ==================== 相机朝向/移动复用向量 ====================
87
- this.camDir = new THREE.Vector3();
88
- this.moveDir = new THREE.Vector3();
89
- this.targetQuat = new THREE.Quaternion();
90
- this.targetMat = new THREE.Matrix4();
422
+ this.camDir = new THREE3.Vector3();
423
+ this.moveDir = new THREE3.Vector3();
424
+ this.targetQuat = new THREE3.Quaternion();
425
+ this.targetMat = new THREE3.Matrix4();
91
426
  this.rotationSpeed = 10;
92
- this.DIR_FWD = new THREE.Vector3(0, 0, -1);
93
- this.DIR_BKD = new THREE.Vector3(0, 0, 1);
94
- this.DIR_LFT = new THREE.Vector3(-1, 0, 0);
95
- this.DIR_RGT = new THREE.Vector3(1, 0, 0);
96
- this.DIR_UP = new THREE.Vector3(0, 1, 0);
427
+ this.DIR_FWD = new THREE3.Vector3(0, 0, -1);
428
+ this.DIR_BKD = new THREE3.Vector3(0, 0, 1);
429
+ this.DIR_LFT = new THREE3.Vector3(-1, 0, 0);
430
+ this.DIR_RGT = new THREE3.Vector3(1, 0, 0);
431
+ this.DIR_UP = new THREE3.Vector3(0, 1, 0);
97
432
  // ==================== 射线检测 ====================
98
- this._personToCam = new THREE.Vector3();
99
- this._originTmp = new THREE.Vector3();
100
- this._raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0));
101
- this._raycasterPersonToCam = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3());
102
- /**
103
- * 根据按键设置人物动画
104
- */
433
+ this._personToCam = new THREE3.Vector3();
434
+ this._originTmp = new THREE3.Vector3();
435
+ this._raycaster = new THREE3.Raycaster(
436
+ new THREE3.Vector3(),
437
+ new THREE3.Vector3(0, -1, 0)
438
+ );
439
+ this._raycasterPersonToCam = new THREE3.Raycaster(
440
+ new THREE3.Vector3(),
441
+ new THREE3.Vector3()
442
+ );
443
+ this.centerRay = new THREE3.Raycaster();
444
+ this.centerMouse = new THREE3.Vector2();
445
+ // ==================== 物理与碰撞检测 ====================
446
+ this.ensureAttributesMinimal = (geom) => {
447
+ if (!geom.attributes.position) return null;
448
+ if (!geom.attributes.normal) geom.computeVertexNormals();
449
+ if (!geom.attributes.uv) {
450
+ const count = geom.attributes.position.count;
451
+ const dummyUV = new Float32Array(count * 2);
452
+ geom.setAttribute("uv", new THREE3.BufferAttribute(dummyUV, 2));
453
+ }
454
+ return geom;
455
+ };
105
456
  this.setAnimationByPressed = () => {
106
- this._maxCamDistance = this.orginMaxCamDistance;
457
+ this.maxCamDistance = this.orginMaxCamDistance;
458
+ if (this.isMovingToBoardingPoint) {
459
+ this.isMovingToBoardingPoint = false;
460
+ this.boardingWaypoints = [];
461
+ this.currentWaypointIndex = 0;
462
+ this.boardingTargetDir = null;
463
+ }
464
+ if (this.isExitAnimPlaying) {
465
+ this.isExitAnimPlaying = false;
466
+ this.closeExitDoorTriggered = false;
467
+ }
468
+ if (this.isBoardingAnimPlaying) {
469
+ this.isBoardingAnimPlaying = false;
470
+ this.closeDoorTriggered = false;
471
+ }
472
+ if (this.closeVehicleDoorTimer) {
473
+ clearTimeout(this.closeVehicleDoorTimer);
474
+ this.closeVehicleDoorTimer = null;
475
+ }
107
476
  if (this.isFlying) {
108
477
  if (!this.fwdPressed) {
109
478
  this.playPersonAnimationByName("flyidle");
110
479
  return;
111
480
  }
112
481
  this.playPersonAnimationByName("flying");
113
- this._maxCamDistance = this.orginMaxCamDistance * 2;
482
+ this.maxCamDistance = this.orginMaxCamDistance * 2;
114
483
  return;
115
484
  }
116
485
  if (this.playerIsOnGround) {
@@ -119,11 +488,15 @@ var PlayerController = class {
119
488
  return;
120
489
  }
121
490
  if (this.fwdPressed) {
122
- this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
491
+ this.playPersonAnimationByName(
492
+ this.shiftPressed ? "running" : "walking"
493
+ );
123
494
  return;
124
495
  }
125
496
  if (!this.isFirstPerson && (this.lftPressed || this.rgtPressed || this.bkdPressed)) {
126
- this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
497
+ this.playPersonAnimationByName(
498
+ this.shiftPressed ? "running" : "walking"
499
+ );
127
500
  return;
128
501
  }
129
502
  if (this.lftPressed) {
@@ -148,37 +521,46 @@ var PlayerController = class {
148
521
  }, 200);
149
522
  };
150
523
  // ==================== 事件处理 ====================
151
- /**
152
- * 键盘按下事件
153
- */
154
524
  this._boundOnKeydown = async (e) => {
155
525
  if (e.ctrlKey && ["KeyW", "KeyA", "KeyS", "KeyD"].includes(e.code)) {
156
526
  e.preventDefault();
157
527
  }
158
528
  switch (e.code) {
159
529
  case "KeyW":
530
+ case "ArrowUp":
160
531
  this.fwdPressed = true;
161
532
  this.setAnimationByPressed();
162
533
  break;
163
534
  case "KeyS":
535
+ case "ArrowDown":
164
536
  this.bkdPressed = true;
165
537
  this.setAnimationByPressed();
166
538
  break;
167
539
  case "KeyD":
540
+ case "ArrowRight":
168
541
  this.rgtPressed = true;
169
542
  this.setAnimationByPressed();
170
543
  break;
171
544
  case "KeyA":
545
+ case "ArrowLeft":
172
546
  this.lftPressed = true;
173
547
  this.setAnimationByPressed();
174
548
  break;
175
549
  case "ShiftLeft":
550
+ case "ShiftRight":
176
551
  this.shiftPressed = true;
177
552
  this.setAnimationByPressed();
178
553
  this.controls.mouseButtons = { LEFT: 2, MIDDLE: 1, RIGHT: 0 };
179
554
  break;
180
555
  case "Space":
556
+ if (this.isMovingToBoardingPoint) {
557
+ this.isMovingToBoardingPoint = false;
558
+ this.boardingWaypoints = [];
559
+ this.currentWaypointIndex = 0;
560
+ this.boardingTargetDir = null;
561
+ }
181
562
  this.spacePressed = true;
563
+ if (this.controllerMode == 1) return;
182
564
  if (!this.playerIsOnGround || this.isFlying) return;
183
565
  const next = this.personActions?.get("jumping");
184
566
  if (next && this.actionState === next) return;
@@ -193,39 +575,48 @@ var PlayerController = class {
193
575
  this.changeView();
194
576
  break;
195
577
  case "KeyF":
196
- this.isFlying = !this.isFlying;
197
- this.setAnimationByPressed();
198
- if (!this.isFlying && !this.playerIsOnGround) {
199
- this.playPersonAnimationByName("jumping");
578
+ if (this.controllerMode == 0 && this.playerFlyEnabled) {
579
+ this.isFlying = !this.isFlying;
580
+ this.setAnimationByPressed();
581
+ if (!this.isFlying && !this.playerIsOnGround) {
582
+ this.playPersonAnimationByName("jumping");
583
+ }
200
584
  }
201
585
  break;
202
586
  case "KeyE":
203
- this.setDrive();
587
+ if (this.isFlying) return;
588
+ if (this.controllerMode == 0) {
589
+ this.enterVehicle();
590
+ } else {
591
+ this.exitVehicle();
592
+ }
204
593
  break;
205
594
  }
206
595
  };
207
- /**
208
- * 键盘抬起事件
209
- */
210
596
  this._boundOnKeyup = (e) => {
211
597
  switch (e.code) {
212
598
  case "KeyW":
599
+ case "ArrowUp":
213
600
  this.fwdPressed = false;
214
601
  this.setAnimationByPressed();
215
602
  break;
216
603
  case "KeyS":
604
+ case "ArrowDown":
217
605
  this.bkdPressed = false;
218
606
  this.setAnimationByPressed();
219
607
  break;
220
608
  case "KeyD":
609
+ case "ArrowRight":
221
610
  this.rgtPressed = false;
222
611
  this.setAnimationByPressed();
223
612
  break;
224
613
  case "KeyA":
614
+ case "ArrowLeft":
225
615
  this.lftPressed = false;
226
616
  this.setAnimationByPressed();
227
617
  break;
228
618
  case "ShiftLeft":
619
+ case "ShiftRight":
229
620
  this.shiftPressed = false;
230
621
  this.setAnimationByPressed();
231
622
  this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
@@ -238,23 +629,14 @@ var PlayerController = class {
238
629
  break;
239
630
  }
240
631
  };
241
- /**
242
- * 鼠标移动事件
243
- */
244
632
  this._mouseMove = (e) => {
245
633
  if (document.pointerLockElement !== document.body) return;
246
634
  this.setToward(e.movementX, e.movementY, 1e-4);
247
635
  };
248
- /**
249
- * 鼠标点击事件
250
- */
251
- this._mouseClick = (e) => {
636
+ this._mouseClick = (_e) => {
252
637
  this.setPointerLock();
253
638
  };
254
639
  // ==================== 移动端控制 ====================
255
- /**
256
- * 指针按下事件
257
- */
258
640
  this.onPointerDown = (e) => {
259
641
  if (e.pointerType !== "touch") return;
260
642
  this.isLookDown = true;
@@ -264,9 +646,6 @@ var PlayerController = class {
264
646
  this.lookAreaEl?.setPointerCapture?.(e.pointerId);
265
647
  e.preventDefault();
266
648
  };
267
- /**
268
- * 指针移动事件
269
- */
270
649
  this.onPointerMove = (e) => {
271
650
  if (!this.isLookDown || e.pointerId !== this.lookPointerId) return;
272
651
  const dx = e.clientX - this.lastTouchX;
@@ -276,9 +655,6 @@ var PlayerController = class {
276
655
  this.setInput({ lookDeltaX: dx, lookDeltaY: dy });
277
656
  e.preventDefault();
278
657
  };
279
- /**
280
- * 指针抬起事件
281
- */
282
658
  this.onPointerUp = (e) => {
283
659
  if (e.pointerId !== this.lookPointerId) return;
284
660
  this.isLookDown = false;
@@ -289,29 +665,27 @@ var PlayerController = class {
289
665
  this._raycasterPersonToCam.firstHitOnly = true;
290
666
  }
291
667
  // ==================== 初始化相关方法 ====================
292
- /**
293
- * 初始化控制器
294
- */
295
668
  async init(opts, callback) {
296
669
  this.scene = opts.scene;
297
670
  this.camera = opts.camera;
298
671
  this.camera.rotation.order = "YXZ";
299
672
  this.controls = opts.controls;
300
673
  this.playerModel = opts.playerModel;
301
- this.initPos = opts.initPos ?? new THREE.Vector3(0, 0, 0);
674
+ this.initPos = opts.initPos ?? new THREE3.Vector3(0, 0, 0);
302
675
  this.mouseSensity = opts.mouseSensity ?? 5;
303
676
  const s = this.playerModel.scale;
304
- this.visualizeDepth = 0 * s;
305
677
  this.gravity = (opts.playerModel.gravity ?? -2400) * s;
306
- this.jumpHeight = (opts.playerModel.jumpHeight ?? 800) * s;
307
- this.originPlayerSpeed = (opts.playerModel.speed ?? 400) * s;
308
- this.playerSpeed = this.originPlayerSpeed;
678
+ this.jumpHeight = (opts.playerModel.jumpHeight ?? 600) * s;
679
+ this.playerSpeed = (opts.playerModel.speed ?? 300) * s;
680
+ this.playerFlySpeed = (opts.playerModel.playerFlySpeed ?? 2100) * s;
681
+ this.curPlayerSpeed = this.playerSpeed;
309
682
  this.playerModel.rotateY = opts.playerModel.rotateY ?? 0;
683
+ this.playerFlyEnabled = opts.playerModel.flyEnabled ?? true;
310
684
  this._camCollisionLerp = 0.18;
311
685
  this._camEpsilon = 35 * s;
312
- this._minCamDistance = (opts.minCamDistance ?? 100) * s;
313
- this._maxCamDistance = (opts.maxCamDistance ?? 440) * s;
314
- this.orginMaxCamDistance = this._maxCamDistance;
686
+ this.minCamDistance = (opts.minCamDistance ?? 100) * s;
687
+ this.maxCamDistance = (opts.maxCamDistance ?? 440) * s;
688
+ this.orginMaxCamDistance = this.maxCamDistance;
315
689
  this.thirdMouseMode = opts.thirdMouseMode ?? 1;
316
690
  this.enableZoom = opts.enableZoom ?? false;
317
691
  const isMobileDevice = () => navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
@@ -320,47 +694,125 @@ var PlayerController = class {
320
694
  await this.initMobileControls();
321
695
  }
322
696
  await this.createBVH(opts.colliderMeshUrl);
323
- this.createPlayer();
324
697
  await this.loadPersonGLB();
325
- if (this.isFirstPerson && this.person) {
326
- this.person.add(this.camera);
327
- }
328
698
  this.onAllEvent();
329
699
  this.setCameraPos();
330
700
  this.setControls();
331
701
  if (callback) callback();
702
+ this.enableOverShoulderView = opts.enableOverShoulderView ?? false;
703
+ this.setOverShoulderView(this.enableOverShoulderView);
704
+ }
705
+ setOverShoulderView(enable) {
706
+ if (!enable || this.controllerMode == 1) {
707
+ this.camera.clearViewOffset();
708
+ return;
709
+ }
710
+ const w = window.innerWidth;
711
+ const h = window.innerHeight;
712
+ this.camera.setViewOffset(
713
+ w,
714
+ h,
715
+ -w * -0.15,
716
+ 0,
717
+ w,
718
+ h
719
+ );
332
720
  }
333
- /**
334
- * 初始化加载器
335
- */
336
721
  async initLoader() {
337
722
  const dracoLoader = new DRACOLoader();
338
723
  dracoLoader.setDecoderPath("https://unpkg.com/three@0.180.0/examples/jsm/libs/draco/gltf/");
339
724
  dracoLoader.setDecoderConfig({ type: "js" });
340
725
  this.loader.setDRACOLoader(dracoLoader);
341
726
  }
727
+ async initRapier() {
728
+ if (this.RAPIER) return;
729
+ this.RAPIER = await import("@dimforge/rapier3d-compat");
730
+ await this.RAPIER.init();
731
+ const gravity = new this.RAPIER.Vector3(0, -9.81, 0);
732
+ this.world = new this.RAPIER.World(gravity);
733
+ this.world.maxCcdSubsteps = 2;
734
+ const addGeometryAsTrimesh = (RAPIER, world, geom) => {
735
+ let geometry = geom.index ? geom.clone().toNonIndexed() : geom.clone();
736
+ const posAttr = geometry.attributes.position;
737
+ const vertexCount = posAttr.count;
738
+ if (vertexCount % 3 !== 0) {
739
+ console.warn("\u9876\u70B9\u6570\u4E0D\u662F3\u7684\u500D\u6570\uFF0C\u4E09\u89D2\u5F62\u53EF\u80FD\u4E0D\u5B8C\u6574");
740
+ }
741
+ const vertices = new Float32Array(vertexCount * 3);
742
+ const tmp = new THREE3.Vector3();
743
+ for (let i = 0; i < vertexCount; i++) {
744
+ tmp.fromBufferAttribute(posAttr, i);
745
+ vertices[i * 3 + 0] = tmp.x;
746
+ vertices[i * 3 + 1] = tmp.y;
747
+ vertices[i * 3 + 2] = tmp.z;
748
+ }
749
+ const indices = vertexCount > 65535 ? new Uint32Array(vertexCount) : new Uint16Array(vertexCount);
750
+ for (let i = 0; i < vertexCount; i++) indices[i] = i;
751
+ const bodyDesc = RAPIER.RigidBodyDesc.fixed();
752
+ const body = world.createRigidBody(bodyDesc);
753
+ const colliderDesc = RAPIER.ColliderDesc.trimesh(vertices, indices).setRestitution(0).setFriction(0.8);
754
+ world.createCollider(colliderDesc, body);
755
+ };
756
+ for (const g of this.collected) {
757
+ addGeometryAsTrimesh(this.RAPIER, this.world, g);
758
+ }
759
+ const groundDesc = this.RAPIER.RigidBodyDesc.fixed();
760
+ const groundBody = this.world.createRigidBody(groundDesc);
761
+ groundBody.userData = { outOfBounds: true };
762
+ }
342
763
  // ==================== 玩家模型相关方法 ====================
343
- /**
344
- * 加载玩家模型与动画
345
- */
346
764
  async loadPersonGLB() {
347
765
  try {
348
- const gltf = await this.loader.loadAsync(this.playerModel.url);
766
+ const gltf = await this.loader.loadAsync(
767
+ this.playerModel.url
768
+ );
349
769
  this.person = gltf.scene;
350
- const sc = this.playerModel.scale;
351
- const h = this.playerHeight * sc;
352
- this.person.scale.set(sc, sc, sc);
770
+ const { size } = this.getBbox(this.person);
771
+ const ratio = this.playerHeight / size.y;
772
+ const power = Math.round(Math.log10(ratio));
773
+ const modelScale = Math.pow(10, power);
774
+ this.playerRadius = Number(Math.min(size.x, size.z).toFixed(0)) * modelScale;
775
+ this.playerHeight = Number(size.y.toFixed(0)) * modelScale;
776
+ const scale = this.playerModel.scale;
777
+ const material = new THREE3.MeshStandardMaterial({
778
+ color: new THREE3.Color(1, 0, 0),
779
+ shadowSide: THREE3.DoubleSide,
780
+ depthTest: false,
781
+ transparent: true,
782
+ opacity: this.displayPlayer ? 0.5 : 0,
783
+ wireframe: true,
784
+ depthWrite: false
785
+ });
786
+ const r = this.playerRadius * scale;
787
+ const h = this.playerHeight * scale;
788
+ this.player = new THREE3.Mesh(
789
+ new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75),
790
+ material
791
+ );
792
+ this.player.geometry.translate(0, -h * 0.25, 0);
793
+ this.player.capsuleInfo = {
794
+ radius: r,
795
+ segment: new THREE3.Line3(
796
+ new THREE3.Vector3(),
797
+ new THREE3.Vector3(0, -h * 0.5, 0)
798
+ )
799
+ };
800
+ this.player.name = "capsule";
801
+ this.scene.add(this.player);
802
+ this.reset();
803
+ this.player.rotateY(this.playerModel.rotateY ?? 0);
804
+ this.person.scale.multiplyScalar(modelScale * scale);
353
805
  this.person.position.set(0, -h * 0.75, 0);
354
806
  this.person.traverse((child) => {
355
- if (child.isMesh) {
356
- child.castShadow = true;
357
- child.receiveShadow = true;
807
+ if (child.name == this.playerModel?.headObjName) {
808
+ this.personHead = child;
358
809
  }
359
810
  });
360
811
  this.player.add(this.person);
361
812
  this.reset();
362
- this.personMixer = new THREE.AnimationMixer(this.person);
813
+ this.personMixer = new THREE3.AnimationMixer(this.person);
363
814
  const animations = gltf.animations ?? [];
815
+ console.log("animations", animations);
364
816
  this.personActions = /* @__PURE__ */ new Map();
365
817
  const animationMappings = [
366
818
  [this.playerModel.idleAnim, "idle"],
@@ -371,7 +823,9 @@ var PlayerController = class {
371
823
  [this.playerModel.jumpAnim, "jumping"],
372
824
  [this.playerModel.runAnim, "running"],
373
825
  [this.playerModel.flyIdleAnim || this.playerModel.idleAnim, "flyidle"],
374
- [this.playerModel.flyAnim || this.playerModel.idleAnim, "flying"]
826
+ [this.playerModel.flyAnim || this.playerModel.idleAnim, "flying"],
827
+ [this.playerModel.enterCarAnim || this.playerModel.idleAnim, "enterCar"],
828
+ [this.playerModel.exitCarAnim || this.playerModel.idleAnim, "exitCar"]
375
829
  ];
376
830
  const findClip = (name) => animations.find((a) => a.name === name);
377
831
  for (const [clipName, actionName] of animationMappings) {
@@ -379,11 +833,11 @@ var PlayerController = class {
379
833
  if (!clip) continue;
380
834
  const action = this.personMixer.clipAction(clip);
381
835
  if (actionName === "jumping") {
382
- action.setLoop(THREE.LoopOnce, 1);
836
+ action.setLoop(THREE3.LoopOnce, 1);
383
837
  action.clampWhenFinished = true;
384
838
  action.setEffectiveTimeScale(1.2);
385
839
  } else {
386
- action.setLoop(THREE.LoopRepeat, Infinity);
840
+ action.setLoop(THREE3.LoopRepeat, Infinity);
387
841
  action.clampWhenFinished = false;
388
842
  action.setEffectiveTimeScale(1);
389
843
  }
@@ -407,7 +861,9 @@ var PlayerController = class {
407
861
  const finishedAction = ev.action;
408
862
  if (finishedAction === this.jumpAction) {
409
863
  if (this.fwdPressed) {
410
- this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
864
+ this.playPersonAnimationByName(
865
+ this.shiftPressed ? "running" : "walking"
866
+ );
411
867
  return;
412
868
  }
413
869
  if (this.bkdPressed) {
@@ -420,21 +876,70 @@ var PlayerController = class {
420
876
  }
421
877
  this.playPersonAnimationByName("idle");
422
878
  }
879
+ if (finishedAction === this.personActions?.get("enterCar")) {
880
+ this.onEnterCarAnimFinished();
881
+ }
882
+ if (finishedAction === this.personActions?.get("exitCar")) {
883
+ }
423
884
  });
424
885
  } catch (error) {
425
886
  console.error("\u52A0\u8F7D\u73A9\u5BB6\u6A21\u578B\u5931\u8D25:", error);
426
887
  }
427
888
  }
428
- /**
429
- * 平滑切换人物动画
430
- */
889
+ async switchPlayerModel(newPlayerModel) {
890
+ const savedPos = this.player.position.clone();
891
+ const savedQuat = this.player.quaternion.clone();
892
+ const wasFirstPerson = this.isFirstPerson;
893
+ if (wasFirstPerson) {
894
+ this.scene.attach(this.camera);
895
+ }
896
+ if (this.player) {
897
+ this.scene.remove(this.player);
898
+ }
899
+ if (this.person) {
900
+ this.player.remove(this.person);
901
+ this.person = null;
902
+ this.personHead = null;
903
+ }
904
+ if (this.personMixer) {
905
+ this.personMixer.stopAllAction();
906
+ this.personMixer.uncacheRoot(this.personMixer.getRoot());
907
+ this.personMixer = void 0;
908
+ this.personActions = void 0;
909
+ }
910
+ const ratio = newPlayerModel.scale / this.playerModel.scale;
911
+ this.playerModel = { ...this.playerModel, ...newPlayerModel };
912
+ this.gravity *= ratio;
913
+ this.jumpHeight *= ratio;
914
+ this.playerSpeed *= ratio;
915
+ this.playerFlySpeed *= ratio;
916
+ this.curPlayerSpeed *= ratio;
917
+ this._camEpsilon *= ratio;
918
+ this.minCamDistance *= ratio;
919
+ this.maxCamDistance *= ratio;
920
+ this.orginMaxCamDistance *= ratio;
921
+ await this.loadPersonGLB();
922
+ this.player.position.copy(savedPos);
923
+ this.player.quaternion.copy(savedQuat);
924
+ if (wasFirstPerson) {
925
+ this.setFirstPersonCamera();
926
+ }
927
+ this.setDebug(this.displayCollider);
928
+ }
431
929
  playPersonAnimationByName(name, fade = 0.18) {
432
930
  if (!this.personActions || this.ctPressed) return;
433
931
  const next = this.personActions.get(name);
434
932
  if (!next || this.actionState === next) return;
933
+ const duration = next.getClip().duration;
435
934
  const prev = this.actionState;
436
935
  next.reset();
437
936
  next.setEffectiveWeight(1);
937
+ if (name == "enterCar" || name == "exitCar") {
938
+ const enterTime = this.activeVehicle?.enterVehicleTime ?? 1.5;
939
+ next.setEffectiveTimeScale(duration / enterTime);
940
+ next.setLoop(THREE3.LoopOnce, 1);
941
+ next.clampWhenFinished = true;
942
+ }
438
943
  next.play();
439
944
  if (prev && prev !== next) {
440
945
  prev.fadeOut(fade);
@@ -444,106 +949,521 @@ var PlayerController = class {
444
949
  }
445
950
  this.actionState = next;
446
951
  }
447
- /**
448
- * 创建玩家胶囊体
449
- */
450
- createPlayer() {
451
- const material = new THREE.MeshStandardMaterial({
452
- color: new THREE.Color(1, 0, 0),
453
- shadowSide: THREE.DoubleSide,
454
- depthTest: false,
455
- transparent: true,
456
- opacity: this.displayPlayer ? 0.5 : 0,
457
- wireframe: true,
458
- depthWrite: false
459
- });
460
- const r = this.playerRadius * this.playerModel.scale;
461
- const h = this.playerHeight * this.playerModel.scale;
462
- this.player = new THREE.Mesh(new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75), material);
463
- this.player.geometry.translate(0, -h * 0.25, 0);
464
- this.player.capsuleInfo = {
465
- radius: r,
466
- segment: new THREE.Line3(new THREE.Vector3(), new THREE.Vector3(0, -h * 0.5, 0))
467
- };
468
- this.player.name = "capsule";
469
- this.scene.add(this.player);
470
- this.reset();
471
- this.player.rotateY(this.playerModel.rotateY ?? 0);
472
- }
473
952
  // ==================== 车辆模型相关 ====================
474
953
  /**
475
- * 加载车辆模型与动画
954
+ * 加载车辆模型
476
955
  */
477
- async loadVehicleModel(params) {
956
+ async loadVehicleModel(opts) {
478
957
  try {
479
- const { url, position, scale = 1 } = params;
480
- const gltf = await this.loader.loadAsync(url);
481
- this.vehicle = gltf.scene;
482
- this.vehicle.scale.set(scale, scale, scale);
483
- this.vehicle.position.copy(position);
484
- this.vehicle.traverse((child) => {
485
- if (child.isMesh) {
486
- child.castShadow = true;
487
- child.receiveShadow = true;
488
- }
489
- });
490
- this.scene.add(this.vehicle);
491
- const animations = gltf.animations ?? [];
492
- this.vehicleActions = /* @__PURE__ */ new Map();
493
- this.vehicleMixer = new THREE.AnimationMixer(this.vehicle);
494
- const animationMappings = [
495
- [params.animations?.openDoorAnim ?? "", "open_door"],
496
- [params.animations?.wheelsTurnAnim ?? "", "wheels_turn"],
497
- [params.animations?.turnLeftAnim ?? "", "turn_left"],
498
- [params.animations?.turnRightAnim ?? "", "turn_right"]
499
- ];
958
+ if (!this.playerModel.enterCarAnim) {
959
+ return console.warn("\u672A\u914D\u7F6E\u4E0A\u8F66\u52A8\u753B\uFF0C\u4E0D\u6267\u884C\u8F66\u8F86\u76F8\u5173\u903B\u8F91");
960
+ }
961
+ await this.initRapier();
962
+ if (!this.world) return;
963
+ const scale = opts.scale ?? 1;
964
+ const chassisRatio = opts.chassisRatio ?? 0.2;
965
+ const suspensionRestLengthRatio = opts.suspensionRestLengthRatio ?? 0.2;
966
+ const speedMultiplier = opts.speedMultiplier ?? 1;
967
+ const followVehicleDirection = opts.followVehicleDirection ?? true;
968
+ this.vehicleParams.power.accelerateForce = 50 * scale;
969
+ this.vehicleParams.power.brakeForce = 200 * scale;
970
+ this.vehicleParams.power.maxSpeed = 1e4 * scale;
971
+ this.vehicleParams.followVehicleDirection = followVehicleDirection;
972
+ const vehicleModel = await this.loader.loadAsync(opts.url);
973
+ const { size: originalSize } = this.getBbox(vehicleModel.scene);
974
+ const ratio = this.vehicleLength / Math.max(originalSize.x, originalSize.y, originalSize.z);
975
+ const power = Math.round(Math.log10(ratio));
976
+ const modelScale = Math.pow(10, power);
977
+ const vehicleMixer = new THREE3.AnimationMixer(vehicleModel.scene);
978
+ const animations = vehicleModel.animations ?? [];
979
+ const vehicleActions = /* @__PURE__ */ new Map();
500
980
  const findClip = (name) => animations.find((a) => a.name === name);
501
- for (const [clipName, actionName] of animationMappings) {
502
- const clip = findClip(clipName);
503
- if (!clip) continue;
504
- const action = this.vehicleMixer.clipAction(clip);
505
- action.setLoop(THREE.LoopOnce, 1);
981
+ const openDoorClip = findClip(opts.animations?.openDoorAnim || "");
982
+ if (openDoorClip) {
983
+ const action = vehicleMixer.clipAction(openDoorClip);
984
+ action.setLoop(THREE3.LoopOnce, 1);
506
985
  action.clampWhenFinished = true;
507
- action.setEffectiveTimeScale(2);
986
+ action.setEffectiveTimeScale(openDoorClip.duration);
508
987
  action.enabled = true;
509
988
  action.setEffectiveWeight(0);
510
- this.vehicleActions.set(actionName, action);
989
+ vehicleActions.set("openDoor", action);
511
990
  }
512
- console.log("\u5F00\u95E8\u52A8\u753B", this.vehicleActions.get("open_door"));
991
+ const wheelObjects = [];
992
+ for (const wheelName of opts.wheelsNames) {
993
+ let found = false;
994
+ vehicleModel.scene.traverse((child) => {
995
+ if (child.name === wheelName && !found) {
996
+ wheelObjects.push(child);
997
+ found = true;
998
+ }
999
+ });
1000
+ if (!found) console.warn(`\u672A\u627E\u5230\u8F6E\u5B50: ${wheelName}`);
1001
+ }
1002
+ const tempGroup = new THREE3.Group();
1003
+ this.scene.add(tempGroup);
1004
+ vehicleModel.scene.scale.multiplyScalar(modelScale * scale);
1005
+ vehicleModel.scene.rotateY(this.vehicleParams.model.rotation);
1006
+ const { size, bbox, center } = this.getBbox(vehicleModel.scene);
1007
+ vehicleModel.scene.position.set(-center.x, -center.y, -center.z);
1008
+ tempGroup.add(vehicleModel.scene);
1009
+ tempGroup.updateMatrixWorld(true);
1010
+ const wheelsInfo = [];
1011
+ let wheelRadius = 0;
1012
+ let wheelWidth = 0;
1013
+ let suspensionRestLength = 0;
1014
+ let chassisHeight = 0;
1015
+ let wheelSizeInit = false;
1016
+ for (let i = 0; i < wheelObjects.length; i++) {
1017
+ const wheel = wheelObjects[i];
1018
+ const worldPos = new THREE3.Vector3();
1019
+ const worldQuat = new THREE3.Quaternion();
1020
+ const worldScale = new THREE3.Vector3();
1021
+ wheel.getWorldPosition(worldPos);
1022
+ wheel.getWorldQuaternion(worldQuat);
1023
+ wheel.getWorldScale(worldScale);
1024
+ if (!wheelSizeInit) {
1025
+ const { size: ws } = this.getBbox(wheel);
1026
+ wheelRadius = Number((Math.max(ws.x, ws.y, ws.z) / 2).toFixed(2));
1027
+ wheelWidth = Number(Math.min(ws.x, ws.y, ws.z).toFixed(2));
1028
+ suspensionRestLength = Number((wheelRadius * 2 * suspensionRestLengthRatio).toFixed(2));
1029
+ chassisHeight = Number((wheelRadius * 2 * chassisRatio).toFixed(2));
1030
+ wheelSizeInit = true;
1031
+ }
1032
+ wheelsInfo.push({
1033
+ axleCs: new THREE3.Vector3(0, 0, -1),
1034
+ position: worldPos,
1035
+ quaternion: worldQuat,
1036
+ scale: worldScale,
1037
+ radius: wheelRadius,
1038
+ width: wheelWidth,
1039
+ suspensionRestLength,
1040
+ object: wheel
1041
+ });
1042
+ }
1043
+ tempGroup.remove(vehicleModel.scene);
1044
+ this.scene.remove(tempGroup);
1045
+ const vehicleGroup = new THREE3.Group();
1046
+ this.scene.add(vehicleGroup);
1047
+ vehicleGroup.add(vehicleModel.scene);
1048
+ vehicleGroup.updateMatrixWorld(true);
1049
+ const wheelWrappers = [];
1050
+ for (let i = 0; i < wheelsInfo.length; i++) {
1051
+ const wheel = wheelsInfo[i];
1052
+ const localPos = vehicleGroup.worldToLocal(wheel.position.clone());
1053
+ const wheelWrapper = new THREE3.Group();
1054
+ wheelWrapper.position.copy(localPos);
1055
+ const wheelObj = wheelsInfo[i].object;
1056
+ if (wheelObj.parent) wheelObj.parent.remove(wheelObj);
1057
+ wheelObj.position.set(0, 0, 0);
1058
+ wheelObj.quaternion.copy(wheel.quaternion);
1059
+ wheelObj.scale.copy(wheel.scale);
1060
+ wheelObj.updateMatrixWorld();
1061
+ wheelWrapper.add(wheelObj);
1062
+ vehicleGroup.add(wheelWrapper);
1063
+ wheelWrappers.push(wheelWrapper);
1064
+ }
1065
+ const halfExtents = size.clone().multiplyScalar(0.5);
1066
+ halfExtents.y -= chassisHeight / 2;
1067
+ vehicleModel.scene.position.y -= chassisHeight / 2;
1068
+ halfExtents.x *= 0.95;
1069
+ halfExtents.z *= 0.95;
1070
+ const chassisDesc = this.RAPIER.RigidBodyDesc.dynamic().setTranslation(
1071
+ opts.position.x,
1072
+ opts.position.y,
1073
+ opts.position.z
1074
+ ).setLinearDamping(this.vehicleParams.chassis.linearDamping).setAngularDamping(this.vehicleParams.chassis.angularDamping).setCanSleep(true).setAdditionalMass(10);
1075
+ const chassisBody = this.world.createRigidBody(chassisDesc);
1076
+ const chassisCollider = this.RAPIER.ColliderDesc.cuboid(
1077
+ halfExtents.x,
1078
+ halfExtents.y,
1079
+ halfExtents.z
1080
+ );
1081
+ this.world.createCollider(chassisCollider, chassisBody);
1082
+ if (this.vehicleParams.debug.showPhysicsBox) {
1083
+ const debugBox = new THREE3.Mesh(
1084
+ new THREE3.BoxGeometry(
1085
+ halfExtents.x * 2,
1086
+ halfExtents.y * 2,
1087
+ halfExtents.z * 2
1088
+ ),
1089
+ new THREE3.MeshBasicMaterial({
1090
+ color: 16711680,
1091
+ wireframe: true,
1092
+ transparent: true,
1093
+ opacity: 0.3
1094
+ })
1095
+ );
1096
+ vehicleGroup.add(debugBox);
1097
+ }
1098
+ vehicleGroup.position.copy(opts.position);
1099
+ vehicleGroup.updateMatrixWorld(true);
1100
+ const { vehicle, updateWheelVisuals } = createVehicleController(
1101
+ this.world,
1102
+ chassisBody,
1103
+ wheelWrappers,
1104
+ wheelsInfo
1105
+ );
1106
+ const vehicleInstance = {
1107
+ vehicleGroup,
1108
+ chassisBody,
1109
+ vehicleController: vehicle,
1110
+ updateWheelVisuals,
1111
+ vehicleMixer,
1112
+ vehicleActions,
1113
+ vehiclIsOpenDoor: false,
1114
+ vehicleBBox: bbox.clone(),
1115
+ pathPlanner: new PathPlanner(
1116
+ this._createObstacleCheckerFor(vehicleGroup, bbox, scale),
1117
+ {
1118
+ debugEnabled: false,
1119
+ scene: this.scene,
1120
+ scale: this.playerModel.scale
1121
+ }
1122
+ ),
1123
+ scale,
1124
+ boardingPoint: opts.boardingPoint,
1125
+ seatOffset: opts.seatOffset ?? new THREE3.Vector3(0, 0, 0),
1126
+ enterVehicleTime: 1.5,
1127
+ chassisRatio,
1128
+ suspensionRestLengthRatio,
1129
+ size: {
1130
+ l: Math.max(size.x, size.z),
1131
+ w: Math.min(size.x, size.z),
1132
+ h: size.y
1133
+ },
1134
+ speedMultiplier
1135
+ };
1136
+ this.vehicles.push(vehicleInstance);
1137
+ this.setControllerTransition();
513
1138
  } catch (error) {
514
1139
  console.error("\u52A0\u8F7D\u8F66\u8F86\u6A21\u578B\u5931\u8D25:", error);
515
1140
  }
516
1141
  }
517
- // ==================== 相机与视角控制 ====================
1142
+ getBbox(object) {
1143
+ const bbox = new THREE3.Box3().setFromObject(object);
1144
+ const center = new THREE3.Vector3();
1145
+ const size = new THREE3.Vector3();
1146
+ bbox.getCenter(center);
1147
+ bbox.getSize(size);
1148
+ return { bbox, center, size };
1149
+ }
1150
+ /**
1151
+ * 为指定车辆创建障碍物检测器
1152
+ */
1153
+ _createObstacleCheckerFor(vehicleGroup, bbox, scale) {
1154
+ return {
1155
+ isBlocked: (start, end) => {
1156
+ const vehiclePos = vehicleGroup.position;
1157
+ const vehicleQuat = vehicleGroup.quaternion;
1158
+ const center = new THREE3.Vector3();
1159
+ const size = new THREE3.Vector3();
1160
+ bbox.getCenter(center);
1161
+ bbox.getSize(size);
1162
+ center.applyQuaternion(vehicleQuat).add(vehiclePos);
1163
+ const halfSize = size.clone().multiplyScalar(0.5 * scale);
1164
+ const corners = [];
1165
+ for (let x = -1; x <= 1; x += 2) {
1166
+ for (let y = -1; y <= 1; y += 2) {
1167
+ for (let z = -1; z <= 1; z += 2) {
1168
+ const localCorner = new THREE3.Vector3(
1169
+ halfSize.x * x,
1170
+ halfSize.y * y,
1171
+ halfSize.z * z
1172
+ );
1173
+ const worldCorner = localCorner.applyQuaternion(vehicleQuat).add(center);
1174
+ corners.push(worldCorner);
1175
+ }
1176
+ }
1177
+ }
1178
+ const expandedBBox = new THREE3.Box3();
1179
+ corners.forEach((corner) => expandedBBox.expandByPoint(corner));
1180
+ expandedBBox.expandByScalar(100 * this.playerModel.scale);
1181
+ const direction = new THREE3.Vector3().subVectors(end, start);
1182
+ const length = direction.length();
1183
+ direction.normalize();
1184
+ const ray = new THREE3.Ray(start, direction);
1185
+ const intersection = new THREE3.Vector3();
1186
+ const intersects = ray.intersectBox(expandedBBox, intersection);
1187
+ return intersects !== null && start.distanceTo(intersection) < length;
1188
+ },
1189
+ getNavigationNodes: (start, _goal) => {
1190
+ const nodes = [];
1191
+ const vehiclePos = vehicleGroup.position;
1192
+ const vehicleQuat = vehicleGroup.quaternion;
1193
+ const vehicleForward = new THREE3.Vector3(
1194
+ 0,
1195
+ 0,
1196
+ 1
1197
+ ).applyQuaternion(vehicleQuat);
1198
+ const vehicleRight = new THREE3.Vector3(1, 0, 0).applyQuaternion(
1199
+ vehicleQuat
1200
+ );
1201
+ const bboxSize = new THREE3.Vector3();
1202
+ bbox.getSize(bboxSize);
1203
+ const halfLength = bboxSize.z / 2 * scale;
1204
+ const halfWidth = bboxSize.x / 2 * scale;
1205
+ const bypassMargin = 300 * this.playerModel.scale;
1206
+ const extendedMargin = 500 * this.playerModel.scale;
1207
+ const groundY = start.y;
1208
+ for (const margin of [bypassMargin, extendedMargin]) {
1209
+ nodes.push(
1210
+ vehiclePos.clone().add(
1211
+ vehicleForward.clone().multiplyScalar(halfLength + margin)
1212
+ ).add(
1213
+ vehicleRight.clone().multiplyScalar(-halfWidth - margin)
1214
+ ).setY(groundY)
1215
+ );
1216
+ nodes.push(
1217
+ vehiclePos.clone().add(
1218
+ vehicleForward.clone().multiplyScalar(halfLength + margin)
1219
+ ).add(
1220
+ vehicleRight.clone().multiplyScalar(halfWidth + margin)
1221
+ ).setY(groundY)
1222
+ );
1223
+ nodes.push(
1224
+ vehiclePos.clone().add(
1225
+ vehicleForward.clone().multiplyScalar(-halfLength - margin)
1226
+ ).add(
1227
+ vehicleRight.clone().multiplyScalar(-halfWidth - margin)
1228
+ ).setY(groundY)
1229
+ );
1230
+ nodes.push(
1231
+ vehiclePos.clone().add(
1232
+ vehicleForward.clone().multiplyScalar(-halfLength - margin)
1233
+ ).add(
1234
+ vehicleRight.clone().multiplyScalar(halfWidth + margin)
1235
+ ).setY(groundY)
1236
+ );
1237
+ }
1238
+ return nodes;
1239
+ }
1240
+ };
1241
+ }
1242
+ /**
1243
+ * 开关车门动画(操作当前 activeVehicle)
1244
+ */
1245
+ openVehicleDoor(isOpen = true) {
1246
+ const v = this.activeVehicle;
1247
+ if (!v?.vehicleActions) return;
1248
+ const next = v.vehicleActions.get("openDoor");
1249
+ if (!next) return;
1250
+ const duration = next.getClip().duration;
1251
+ next.reset();
1252
+ next.setEffectiveWeight(1);
1253
+ if (isOpen) {
1254
+ next.setEffectiveTimeScale(duration * 2);
1255
+ next.time = 0;
1256
+ v.vehiclIsOpenDoor = true;
1257
+ } else {
1258
+ next.setEffectiveTimeScale(-duration * 2);
1259
+ next.time = duration;
1260
+ v.vehiclIsOpenDoor = false;
1261
+ }
1262
+ next.setLoop(THREE3.LoopOnce, 1);
1263
+ next.clampWhenFinished = true;
1264
+ next.play();
1265
+ }
1266
+ /**
1267
+ * 上车:自动寻找最近的车辆
1268
+ */
1269
+ enterVehicle() {
1270
+ if (this.vehicles.length === 0 || this.isMovingToBoardingPoint) return;
1271
+ let nearestVehicle = null;
1272
+ let nearestDist = Infinity;
1273
+ let nearBoardingPointWorld = null;
1274
+ for (const v2 of this.vehicles) {
1275
+ const boardingPointLocal = v2.boardingPoint.clone().multiplyScalar(v2.scale);
1276
+ const boardingPointWorld = new THREE3.Vector3();
1277
+ v2.vehicleGroup.localToWorld(
1278
+ boardingPointWorld.copy(boardingPointLocal)
1279
+ );
1280
+ const dist = this.player.position.distanceTo(boardingPointWorld);
1281
+ if (dist < 800 * this.playerModel.scale && dist < nearestDist) {
1282
+ nearestDist = dist;
1283
+ nearestVehicle = v2;
1284
+ nearBoardingPointWorld = boardingPointWorld;
1285
+ }
1286
+ }
1287
+ if (!nearestVehicle || !nearBoardingPointWorld) return;
1288
+ this.activeVehicle = nearestVehicle;
1289
+ const v = nearestVehicle;
1290
+ const vel = v.chassisBody.linvel();
1291
+ const horizSpeed = Math.sqrt(vel.x * vel.x + vel.z * vel.z);
1292
+ if (horizSpeed > 0.1) return;
1293
+ this.boardingPointWorld = nearBoardingPointWorld;
1294
+ const vehicleForward = new THREE3.Vector3(0, 0, 1).applyQuaternion(v.vehicleGroup.quaternion).normalize();
1295
+ const path = v.pathPlanner.findPath(
1296
+ this.player.position.clone(),
1297
+ this.boardingPointWorld
1298
+ );
1299
+ this.boardingWaypoints = path;
1300
+ this.currentWaypointIndex = 0;
1301
+ this.boardingTargetDir = vehicleForward;
1302
+ this.isMovingToBoardingPoint = true;
1303
+ this.playPersonAnimationByName("walking");
1304
+ }
1305
+ /**
1306
+ * 走向上车点
1307
+ */
1308
+ updateMoveToBoardingPoint(delta) {
1309
+ if (!this.isMovingToBoardingPoint || !this.boardingTargetDir || this.boardingWaypoints.length === 0) {
1310
+ return;
1311
+ }
1312
+ if (this.currentWaypointIndex >= this.boardingWaypoints.length) {
1313
+ this.finalizeBoarding(delta);
1314
+ return;
1315
+ }
1316
+ const currentWaypoint = this.boardingWaypoints[this.currentWaypointIndex];
1317
+ const currentPos = this.player.position.clone();
1318
+ const horizontalDistance = new THREE3.Vector2(
1319
+ currentWaypoint.x - currentPos.x,
1320
+ currentWaypoint.z - currentPos.z
1321
+ ).length();
1322
+ const isLastWaypoint = this.currentWaypointIndex === this.boardingWaypoints.length - 1;
1323
+ const waypointThreshold = isLastWaypoint ? 0 : 10 * this.playerModel.scale;
1324
+ if (horizontalDistance > waypointThreshold) {
1325
+ const moveDir = new THREE3.Vector3(
1326
+ currentWaypoint.x - currentPos.x,
1327
+ 0,
1328
+ currentWaypoint.z - currentPos.z
1329
+ ).normalize();
1330
+ const moveDistance = Math.min(
1331
+ this.boardingMoveSpeed * this.playerModel.scale * delta,
1332
+ horizontalDistance
1333
+ );
1334
+ this.player.position.add(moveDir.multiplyScalar(moveDistance));
1335
+ const lookTarget = this.player.position.clone().add(moveDir);
1336
+ this.targetMat.lookAt(
1337
+ this.player.position,
1338
+ lookTarget,
1339
+ this.player.up
1340
+ );
1341
+ this.targetQuat.setFromRotationMatrix(this.targetMat);
1342
+ this.targetQuat.multiply(this.flip180Quat);
1343
+ const rotateAlpha = Math.min(1, this.boardingRotateSpeed * delta);
1344
+ this.player.quaternion.slerp(this.targetQuat, rotateAlpha);
1345
+ } else {
1346
+ this.currentWaypointIndex++;
1347
+ }
1348
+ }
1349
+ /**
1350
+ * 完成上车
1351
+ */
1352
+ finalizeBoarding(delta) {
1353
+ const v = this.activeVehicle;
1354
+ if (!this.boardingTargetDir || !v || !this.isMovingToBoardingPoint) return;
1355
+ const currentDir = new THREE3.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion).normalize();
1356
+ const targetDir = this.boardingTargetDir.clone().normalize();
1357
+ const angleDiff = currentDir.angleTo(targetDir);
1358
+ if (angleDiff > 0.01) {
1359
+ const lookTarget = this.player.position.clone().add(targetDir);
1360
+ this.targetMat.lookAt(
1361
+ this.player.position,
1362
+ lookTarget,
1363
+ this.player.up
1364
+ );
1365
+ this.targetQuat.setFromRotationMatrix(this.targetMat);
1366
+ const rotateAlpha = Math.min(1, this.boardingRotateSpeed * delta);
1367
+ this.player.quaternion.slerp(this.targetQuat, rotateAlpha);
1368
+ } else {
1369
+ this.boardingWaypoints = [];
1370
+ this.currentWaypointIndex = 0;
1371
+ this.boardingTargetDir = null;
1372
+ v.pathPlanner?.clearVisualization();
1373
+ this.playPersonAnimationByName("enterCar");
1374
+ this.isBoardingAnimPlaying = true;
1375
+ this.closeDoorTriggered = false;
1376
+ if (!v.vehiclIsOpenDoor) this.openVehicleDoor();
1377
+ this.player.rotation.copy(v.vehicleGroup.rotation);
1378
+ this.player.quaternion.multiply(this.flip180Quat);
1379
+ }
1380
+ }
1381
+ onEnterCarAnimFinished() {
1382
+ const v = this.activeVehicle;
1383
+ if (!v || !this.isMovingToBoardingPoint) return;
1384
+ this.player.updateMatrixWorld(true);
1385
+ const offsetY = this.boardingPointWorld.y - this.player.position.y;
1386
+ this.controllerMode = 1;
1387
+ this.syncControllerModeBtnEl();
1388
+ this.setOverShoulderView(false);
1389
+ v.vehicleGroup.attach(this.player);
1390
+ this.player.position.add(
1391
+ v.seatOffset.clone().multiplyScalar(v.scale).add(new THREE3.Vector3(0, offsetY, 0))
1392
+ );
1393
+ this.isMovingToBoardingPoint = false;
1394
+ }
518
1395
  /**
519
- * 第一/三人称视角切换
1396
+ * 下车
520
1397
  */
1398
+ exitVehicle() {
1399
+ const v = this.activeVehicle;
1400
+ if (!v) return;
1401
+ this.isMovingToBoardingPoint = false;
1402
+ this.boardingWaypoints = [];
1403
+ this.currentWaypointIndex = 0;
1404
+ this.boardingTargetDir = null;
1405
+ const vel = v.chassisBody.linvel();
1406
+ const horizSpeed = Math.sqrt(vel.x * vel.x + vel.z * vel.z);
1407
+ const isStationary = horizSpeed < 0.1;
1408
+ if (isStationary) {
1409
+ this.playPersonAnimationByName("exitCar");
1410
+ this.isExitAnimPlaying = true;
1411
+ this.closeExitDoorTriggered = false;
1412
+ } else {
1413
+ this.playPersonAnimationByName("idle");
1414
+ }
1415
+ this.openVehicleDoor(true);
1416
+ this.controllerMode = 0;
1417
+ this.syncControllerModeBtnEl();
1418
+ this.setOverShoulderView(this.enableOverShoulderView);
1419
+ this.scene.attach(this.player);
1420
+ if (this.isFirstPerson) {
1421
+ this.setFirstPersonCamera();
1422
+ }
1423
+ this.setControllerTransition();
1424
+ }
1425
+ // ==================== 相机与视角控制 ====================
521
1426
  changeView() {
522
1427
  this.isFirstPerson = !this.isFirstPerson;
523
1428
  if (this.isFirstPerson) {
524
- this.player.attach(this.camera);
525
- this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
526
- this.camera.rotation.set(0, Math.PI, 0);
527
- this.controls.enableZoom = false;
1429
+ this.setFirstPersonCamera();
1430
+ this.setOverShoulderView(false);
528
1431
  } else {
1432
+ this.controls.enabled = true;
529
1433
  this.scene.attach(this.camera);
530
1434
  const worldPos = this.player.position.clone();
531
- const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
1435
+ const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(
1436
+ this.player.quaternion
1437
+ );
532
1438
  const angle = Math.atan2(dir.z, dir.x);
533
- const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, 200 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
1439
+ const offset = new THREE3.Vector3(
1440
+ Math.cos(angle) * 400 * this.playerModel.scale,
1441
+ 200 * this.playerModel.scale,
1442
+ Math.sin(angle) * 400 * this.playerModel.scale
1443
+ );
534
1444
  this.camera.position.copy(worldPos).add(offset);
535
1445
  this.controls.target.copy(worldPos);
536
1446
  this.controls.enableZoom = this.enableZoom;
1447
+ this.setOverShoulderView(this.enableOverShoulderView);
537
1448
  }
538
1449
  this.setPointerLock();
539
1450
  }
540
- setDrive() {
541
- this.controllerMode = 2;
542
- this.person?.attach(this.vehicle);
1451
+ setFirstPersonCamera() {
1452
+ this.controls.enabled = false;
1453
+ if (this.personHead) {
1454
+ this.personHead?.attach(this.camera);
1455
+ this.camera.position.set(0, 10, 20);
1456
+ } else {
1457
+ this.player.attach(this.camera);
1458
+ this.camera.position.set(
1459
+ 0,
1460
+ 40 * this.playerModel.scale,
1461
+ 30 * this.playerModel.scale
1462
+ );
1463
+ }
1464
+ this.camera.rotation.set(0, Math.PI, 0);
1465
+ this.controls.enableZoom = false;
543
1466
  }
544
- /**
545
- * 设置指针锁定
546
- */
547
1467
  setPointerLock() {
548
1468
  if ((this.thirdMouseMode === 0 || this.thirdMouseMode === 1) && !this.isFirstPerson || this.isFirstPerson) {
549
1469
  document.body.requestPointerLock();
@@ -551,33 +1471,38 @@ var PlayerController = class {
551
1471
  document.exitPointerLock();
552
1472
  }
553
1473
  }
554
- /**
555
- * 设置摄像机初始位置
556
- */
557
1474
  setCameraPos() {
558
- if (this.isFirstPerson) {
559
- this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
560
- } else {
561
- const worldPos = this.player.position.clone();
562
- const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
563
- const angle = Math.atan2(dir.z, dir.x);
564
- const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, -100 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
565
- this.camera.position.copy(worldPos).add(offset);
566
- }
567
- this.camera.updateProjectionMatrix();
1475
+ requestAnimationFrame(() => {
1476
+ if (this.isFirstPerson) {
1477
+ this.person.add(this.camera);
1478
+ this.camera.position.set(
1479
+ 0,
1480
+ 40 * this.playerModel.scale,
1481
+ 30 * this.playerModel.scale
1482
+ );
1483
+ } else {
1484
+ const worldPos = this.player.position.clone();
1485
+ const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(
1486
+ this.player.quaternion
1487
+ );
1488
+ const angle = Math.atan2(dir.z, dir.x);
1489
+ const offset = new THREE3.Vector3(
1490
+ Math.cos(angle) * 400 * this.playerModel.scale,
1491
+ 200 * this.playerModel.scale,
1492
+ Math.sin(angle) * 400 * this.playerModel.scale
1493
+ );
1494
+ this.camera.position.copy(worldPos).add(offset);
1495
+ this.controls.enableZoom = this.enableZoom;
1496
+ }
1497
+ this.camera.updateProjectionMatrix();
1498
+ });
568
1499
  }
569
- /**
570
- * 设置控制器
571
- */
572
1500
  setControls() {
573
1501
  this.controls.enableZoom = this.enableZoom;
574
1502
  this.controls.rotateSpeed = this.mouseSensity * 0.05;
575
1503
  this.controls.maxPolarAngle = Math.PI * (300 / 360);
576
1504
  this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
577
1505
  }
578
- /**
579
- * 重置控制器
580
- */
581
1506
  resetControls() {
582
1507
  if (!this.controls) return;
583
1508
  this.controls.enabled = true;
@@ -587,38 +1512,80 @@ var PlayerController = class {
587
1512
  this.controls.enableZoom = true;
588
1513
  this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
589
1514
  }
590
- /**
591
- * 设置朝向
592
- */
593
1515
  setToward(dx, dy, speed) {
594
- if (this.isFirstPerson) {
595
- const yaw = -dx * speed * this.mouseSensity;
596
- const pitch = -dy * speed * this.mouseSensity;
597
- this.player.rotateY(yaw);
598
- this.camera.rotation.x = THREE.MathUtils.clamp(this.camera.rotation.x + pitch, -1.1, 1.4);
1516
+ if (this.controllerMode == 0) {
1517
+ if (this.isFirstPerson) {
1518
+ if (this.isMovingToBoardingPoint) return;
1519
+ const yaw = -dx * speed * this.mouseSensity;
1520
+ const pitch = -dy * speed * this.mouseSensity;
1521
+ this.player.rotateY(yaw);
1522
+ this.camera.rotation.x = THREE3.MathUtils.clamp(
1523
+ this.camera.rotation.x + pitch,
1524
+ -1.1,
1525
+ 1.4
1526
+ );
1527
+ } else {
1528
+ const sensitivity = this.mouseSensity;
1529
+ const deltaX = -dx * speed * sensitivity;
1530
+ const deltaY = -dy * speed * sensitivity;
1531
+ const target = this.player.position.clone();
1532
+ const distance = this.camera.position.distanceTo(target);
1533
+ const currentPosition = this.camera.position.clone().sub(target);
1534
+ let theta = Math.atan2(currentPosition.x, currentPosition.z);
1535
+ let phi = Math.acos(currentPosition.y / distance);
1536
+ theta += deltaX;
1537
+ phi += deltaY;
1538
+ phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
1539
+ const newX = distance * Math.sin(phi) * Math.sin(theta);
1540
+ const newY = distance * Math.cos(phi);
1541
+ const newZ = distance * Math.sin(phi) * Math.cos(theta);
1542
+ this.camera.position.set(
1543
+ target.x + newX,
1544
+ target.y + newY,
1545
+ target.z + newZ
1546
+ );
1547
+ this.camera.lookAt(target);
1548
+ }
599
1549
  } else {
600
- const sensitivity = this.mouseSensity;
601
- const deltaX = -dx * speed * sensitivity;
602
- const deltaY = -dy * speed * sensitivity;
603
- const target = this.player.position.clone();
604
- const distance = this.camera.position.distanceTo(target);
605
- const currentPosition = this.camera.position.clone().sub(target);
606
- let theta = Math.atan2(currentPosition.x, currentPosition.z);
607
- let phi = Math.acos(currentPosition.y / distance);
608
- theta += deltaX;
609
- phi += deltaY;
610
- phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
611
- const newX = distance * Math.sin(phi) * Math.sin(theta);
612
- const newY = distance * Math.cos(phi);
613
- const newZ = distance * Math.sin(phi) * Math.cos(theta);
614
- this.camera.position.set(target.x + newX, target.y + newY, target.z + newZ);
615
- this.camera.lookAt(target);
616
- }
617
- }
618
- // ==================== 物理与碰撞检测 ====================
619
- /**
620
- * 统一属性集合
621
- */
1550
+ const v = this.activeVehicle;
1551
+ if (!v) return;
1552
+ if (this.isFirstPerson) {
1553
+ const yaw = -dx * speed * this.mouseSensity;
1554
+ const pitch = -dy * speed * this.mouseSensity;
1555
+ this.camera.rotation.y = THREE3.MathUtils.clamp(
1556
+ this.camera.rotation.y + yaw,
1557
+ Math.PI * (3 / 4),
1558
+ Math.PI * (5 / 4)
1559
+ );
1560
+ this.camera.rotation.x = THREE3.MathUtils.clamp(
1561
+ this.camera.rotation.x + pitch,
1562
+ 0,
1563
+ Math.PI * (1 / 3)
1564
+ );
1565
+ } else {
1566
+ const sensitivity = this.mouseSensity;
1567
+ const deltaX = -dx * speed * sensitivity;
1568
+ const deltaY = -dy * speed * sensitivity;
1569
+ const target = v.vehicleGroup.position.clone();
1570
+ const distance = this.camera.position.distanceTo(target);
1571
+ const currentPosition = this.camera.position.clone().sub(target);
1572
+ let theta = Math.atan2(currentPosition.x, currentPosition.z);
1573
+ let phi = Math.acos(currentPosition.y / distance);
1574
+ theta += deltaX;
1575
+ phi += deltaY;
1576
+ phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
1577
+ const newX = distance * Math.sin(phi) * Math.sin(theta);
1578
+ const newY = distance * Math.cos(phi);
1579
+ const newZ = distance * Math.sin(phi) * Math.cos(theta);
1580
+ this.camera.position.set(
1581
+ target.x + newX,
1582
+ target.y + newY,
1583
+ target.z + newZ
1584
+ );
1585
+ this.camera.lookAt(target);
1586
+ }
1587
+ }
1588
+ }
622
1589
  unifiedAttribute(collected) {
623
1590
  const attrMap = /* @__PURE__ */ new Map();
624
1591
  const attrConflict = /* @__PURE__ */ new Set();
@@ -638,7 +1605,12 @@ var PlayerController = class {
638
1605
  const itemSize = attr.itemSize;
639
1606
  const normalized = attr.normalized;
640
1607
  if (!attrMap.has(name)) {
641
- attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1, normalized });
1608
+ attrMap.set(name, {
1609
+ itemSize,
1610
+ arrayCtor: ctor,
1611
+ examples: 1,
1612
+ normalized
1613
+ });
642
1614
  } else {
643
1615
  const m = attrMap.get(name);
644
1616
  if (m.itemSize !== itemSize || m.arrayCtor !== ctor || m.normalized !== normalized) {
@@ -665,28 +1637,21 @@ var PlayerController = class {
665
1637
  const meta = attrMap.get(name);
666
1638
  const len = count * meta.itemSize;
667
1639
  const array = new meta.arrayCtor(len);
668
- g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize, meta.normalized));
1640
+ g.setAttribute(
1641
+ name,
1642
+ new THREE3.BufferAttribute(
1643
+ array,
1644
+ meta.itemSize,
1645
+ meta.normalized
1646
+ )
1647
+ );
669
1648
  }
670
1649
  }
671
1650
  }
672
1651
  return collected;
673
1652
  }
674
- /**
675
- * BVH碰撞体构建
676
- */
677
1653
  async createBVH(meshUrl = "") {
678
1654
  await this.initLoader();
679
- const ensureAttributesMinimal = (geom) => {
680
- if (!geom.attributes.position) return null;
681
- if (!geom.attributes.normal) geom.computeVertexNormals();
682
- if (!geom.attributes.uv) {
683
- const count = geom.attributes.position.count;
684
- const dummyUV = new Float32Array(count * 2);
685
- geom.setAttribute("uv", new THREE.BufferAttribute(dummyUV, 2));
686
- }
687
- return geom;
688
- };
689
- let collected = [];
690
1655
  if (meshUrl === "") {
691
1656
  if (this.collider) {
692
1657
  this.scene.remove(this.collider);
@@ -699,68 +1664,435 @@ var PlayerController = class {
699
1664
  let geom = mesh.geometry.clone();
700
1665
  geom.applyMatrix4(mesh.matrixWorld);
701
1666
  if (geom.index) geom = geom.toNonIndexed();
702
- const safe = ensureAttributesMinimal(geom);
703
- if (safe) collected.push(safe);
1667
+ const safe = this.ensureAttributesMinimal(geom);
1668
+ if (safe) this.collected.push(safe);
704
1669
  } catch (e) {
705
1670
  console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
706
1671
  }
707
1672
  }
708
1673
  });
709
- if (!collected.length) return;
710
- collected = this.unifiedAttribute(collected);
1674
+ if (!this.collected.length) return;
1675
+ this.collected = this.unifiedAttribute(this.collected);
711
1676
  } else {
712
1677
  const gltf = await this.loader.loadAsync(meshUrl);
713
- const mesh = gltf.scene.children[0];
714
- mesh.name = "BVH\u52A0\u8F7D\u6A21\u578B";
715
- let geom = mesh.geometry.clone();
716
- geom.applyMatrix4(mesh.matrixWorld);
717
- if (geom.index) geom = geom.toNonIndexed();
718
- const safe = ensureAttributesMinimal(geom);
719
- if (safe) collected.push(safe);
720
- }
721
- const merged = BufferGeometryUtils.mergeGeometries(collected, false);
1678
+ const obj = gltf.scene.children[0];
1679
+ if (obj && obj?.geometry) {
1680
+ const mesh = obj;
1681
+ let geom = mesh.geometry.clone();
1682
+ geom.applyMatrix4(mesh.matrixWorld);
1683
+ if (geom.index) geom = geom.toNonIndexed();
1684
+ const safe = this.ensureAttributesMinimal(geom);
1685
+ if (safe) this.collected.push(safe);
1686
+ } else {
1687
+ obj.traverse((c) => {
1688
+ const mesh = c;
1689
+ if (mesh?.isMesh && mesh.geometry && c.name !== "capsule") {
1690
+ try {
1691
+ let geom = mesh.geometry.clone();
1692
+ geom.applyMatrix4(mesh.matrixWorld);
1693
+ if (geom.index) geom = geom.toNonIndexed();
1694
+ const safe = this.ensureAttributesMinimal(geom);
1695
+ if (safe) this.collected.push(safe);
1696
+ } catch (e) {
1697
+ console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
1698
+ }
1699
+ }
1700
+ });
1701
+ if (!this.collected.length) return;
1702
+ this.collected = this.unifiedAttribute(this.collected);
1703
+ }
1704
+ }
1705
+ const merged = BufferGeometryUtils.mergeGeometries(
1706
+ this.collected,
1707
+ false
1708
+ );
722
1709
  if (!merged) {
723
1710
  console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
724
1711
  return;
725
1712
  }
726
1713
  merged.boundsTree = new MeshBVH(merged, { maxDepth: 100 });
727
- this.collider = new THREE.Mesh(
1714
+ this.collider = new THREE3.Mesh(
728
1715
  merged,
729
- new THREE.MeshBasicMaterial({
1716
+ new THREE3.MeshBasicMaterial({
730
1717
  opacity: 0.5,
731
1718
  transparent: true,
732
- wireframe: true
1719
+ wireframe: true,
1720
+ depthTest: true
733
1721
  })
734
1722
  );
735
1723
  if (this.displayCollider) this.scene.add(this.collider);
736
1724
  if (this.displayVisualizer) {
737
1725
  if (this.visualizer) this.scene.remove(this.visualizer);
738
- this.visualizer = new MeshBVHHelper(this.collider, this.visualizeDepth);
1726
+ this.visualizer = new MeshBVHHelper(this.collider, 0);
739
1727
  this.scene.add(this.visualizer);
740
1728
  }
741
1729
  this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
742
1730
  }
743
- /**
744
- * 获取法线与Y轴的夹角
745
- */
1731
+ createDynamicBVH(objects = []) {
1732
+ if (this.dynamicCollider) {
1733
+ this.scene.remove(this.dynamicCollider);
1734
+ this.dynamicCollider = null;
1735
+ }
1736
+ this.dynamicCollected = [];
1737
+ objects.forEach((object) => {
1738
+ object.traverse((c) => {
1739
+ const mesh = c;
1740
+ if (mesh?.isMesh && mesh.geometry && c.name !== "capsule") {
1741
+ try {
1742
+ let geom = mesh.geometry.clone();
1743
+ geom.applyMatrix4(mesh.matrixWorld);
1744
+ if (geom.index) geom = geom.toNonIndexed();
1745
+ const safe = this.ensureAttributesMinimal(geom);
1746
+ if (safe) this.dynamicCollected.push(safe);
1747
+ } catch (e) {
1748
+ console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
1749
+ }
1750
+ }
1751
+ });
1752
+ });
1753
+ if (!this.dynamicCollected.length) return;
1754
+ this.dynamicCollected = this.unifiedAttribute(this.dynamicCollected);
1755
+ const merged = BufferGeometryUtils.mergeGeometries(
1756
+ this.dynamicCollected,
1757
+ false
1758
+ );
1759
+ if (!merged) {
1760
+ console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
1761
+ return;
1762
+ }
1763
+ merged.boundsTree = new MeshBVH(merged);
1764
+ this.dynamicCollider = new THREE3.Mesh(
1765
+ merged,
1766
+ new THREE3.MeshBasicMaterial({
1767
+ opacity: 0.5,
1768
+ transparent: true,
1769
+ wireframe: true,
1770
+ depthTest: true
1771
+ })
1772
+ );
1773
+ if (this.displayCollider) this.scene.add(this.dynamicCollider);
1774
+ }
746
1775
  getAngleWithYAxis(normal) {
747
1776
  const yAxis = { x: 0, y: 1, z: 0 };
748
1777
  const dotProduct = normal.x * yAxis.x + normal.y * yAxis.y + normal.z * yAxis.z;
749
- const normalMagnitude = Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
1778
+ const normalMagnitude = Math.sqrt(
1779
+ normal.x * normal.x + normal.y * normal.y + normal.z * normal.z
1780
+ );
750
1781
  const cosTheta = dotProduct / normalMagnitude;
751
1782
  return Math.acos(cosTheta);
752
1783
  }
1784
+ // ==================== 设置控制器过渡 ====================
1785
+ setControllerTransition() {
1786
+ if (this.isChangeControllerTransitionTimer) {
1787
+ clearTimeout(this.isChangeControllerTransitionTimer);
1788
+ this.isChangeControllerTransitionTimer = null;
1789
+ }
1790
+ let vGroups = [];
1791
+ for (const v of this.vehicles) {
1792
+ vGroups.push(v.vehicleGroup);
1793
+ }
1794
+ this.createDynamicBVH(vGroups);
1795
+ this.isChangeControllerTransitionTimer = setTimeout(() => {
1796
+ this.isChangeControllerTransitionTimer = null;
1797
+ for (const v of this.vehicles) {
1798
+ this.clearVehicleVelocity(v);
1799
+ }
1800
+ this.createDynamicBVH(vGroups);
1801
+ }, 3e3);
1802
+ }
1803
+ // 清除车辆速度
1804
+ clearVehicleVelocity(v) {
1805
+ if (!v || !this.world || !this.RAPIER) return;
1806
+ const { chassisBody, vehicleController } = v;
1807
+ const ZERO = new this.RAPIER.Vector3(0, 0, 0);
1808
+ chassisBody.setLinvel(ZERO, true);
1809
+ chassisBody.setAngvel(ZERO, true);
1810
+ const BIG_BRAKE = 1e6;
1811
+ for (let i = 0; i < 4; i++) {
1812
+ vehicleController.setWheelEngineForce(i, 0);
1813
+ vehicleController.setWheelBrake(i, BIG_BRAKE);
1814
+ }
1815
+ vehicleController.updateVehicle(1 / 60);
1816
+ this.world.timestep = 1 / 60;
1817
+ this.world.step();
1818
+ chassisBody.setLinvel(ZERO, true);
1819
+ chassisBody.setAngvel(ZERO, true);
1820
+ for (let i = 0; i < 4; i++) {
1821
+ vehicleController.setWheelBrake(i, 0);
1822
+ }
1823
+ }
753
1824
  // ==================== 循环更新 ====================
754
- /**
755
- * 每帧更新
756
- */
757
1825
  async update(delta = clock.getDelta()) {
758
1826
  if (!this.isupdate || !this.player || !this.collider) return;
759
- delta = Math.min(delta, 1 / 30);
1827
+ delta = Math.min(delta, 1 / 40);
1828
+ if (this.controllerMode == 1) {
1829
+ this.updateVehicle(delta);
1830
+ } else {
1831
+ this.updatePlayer(delta);
1832
+ if (this.isChangeControllerTransitionTimer)
1833
+ this.updateVehicleInertia(delta);
1834
+ }
1835
+ }
1836
+ /**
1837
+ * 更新当前驾驶的车辆
1838
+ */
1839
+ updateVehicle(delta) {
1840
+ const v = this.activeVehicle;
1841
+ if (!v || !this.world) return;
1842
+ const { vehicleController, chassisBody, vehicleGroup } = v;
1843
+ const rotation = chassisBody.rotation();
1844
+ const quat = new THREE3.Quaternion(
1845
+ rotation.x,
1846
+ rotation.y,
1847
+ rotation.z,
1848
+ rotation.w
1849
+ );
1850
+ const forward = new THREE3.Vector3(1, 0, 0).applyQuaternion(quat);
1851
+ const slopeAngle = Math.asin(forward.y);
1852
+ let factor = 1;
1853
+ if (slopeAngle < -0.05 && this.fwdPressed) factor = -Math.sin(slopeAngle) * 10;
1854
+ const accelerateForce = this.vehicleParams.power.accelerateForce * v.speedMultiplier;
1855
+ const maxSpeed = this.vehicleParams.power.maxSpeed * v.speedMultiplier;
1856
+ const engineForce = (Number(this.fwdPressed) * accelerateForce - Number(this.bkdPressed) * accelerateForce) * factor;
1857
+ vehicleController.setWheelEngineForce(0, engineForce);
1858
+ vehicleController.setWheelEngineForce(1, engineForce);
1859
+ vehicleController.setWheelEngineForce(2, engineForce);
1860
+ vehicleController.setWheelEngineForce(3, engineForce);
1861
+ const wheelBrake = Number(this.spacePressed) * this.vehicleParams.power.brakeForce * delta;
1862
+ vehicleController.setWheelBrake(0, wheelBrake);
1863
+ vehicleController.setWheelBrake(1, wheelBrake);
1864
+ vehicleController.setWheelBrake(2, wheelBrake);
1865
+ vehicleController.setWheelBrake(3, wheelBrake);
1866
+ const currentSteering = vehicleController.wheelSteering(0) || 0;
1867
+ const steerDirection = Number(this.lftPressed) - Number(this.rgtPressed);
1868
+ let steerSpeed;
1869
+ if (steerDirection === 0) {
1870
+ steerSpeed = this.vehicleParams.steering.steerReturnSpeed || 0.15;
1871
+ } else {
1872
+ steerSpeed = this.vehicleParams.steering.steerSpeed || 0.08;
1873
+ }
1874
+ const steerLerpFactor = 1 - Math.pow(1 - steerSpeed, delta);
1875
+ const targetSteering = this.vehicleParams.steering.maxSteerAngle * steerDirection;
1876
+ const steering = THREE3.MathUtils.lerp(
1877
+ currentSteering,
1878
+ targetSteering,
1879
+ steerLerpFactor
1880
+ );
1881
+ vehicleController.setWheelSteering(0, steering);
1882
+ vehicleController.setWheelSteering(1, steering);
1883
+ if ((this.rgtPressed || this.lftPressed) && this.shiftPressed) {
1884
+ vehicleController.setWheelSideFrictionStiffness(2, 0.5);
1885
+ vehicleController.setWheelSideFrictionStiffness(3, 0.5);
1886
+ } else {
1887
+ vehicleController.setWheelSideFrictionStiffness(2, 2);
1888
+ vehicleController.setWheelSideFrictionStiffness(3, 2);
1889
+ }
1890
+ this.updateVehicleInertia(delta);
1891
+ if (!this.isFirstPerson) {
1892
+ const lookTarget = vehicleGroup.position.clone();
1893
+ this.camera.position.sub(this.controls.target);
1894
+ this.controls.target.copy(lookTarget);
1895
+ this.camera.position.add(lookTarget);
1896
+ this.controls.update();
1897
+ const velocity = chassisBody.linvel();
1898
+ const currentSpeed = Math.sqrt(
1899
+ velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z
1900
+ );
1901
+ const speedRatio = Math.min(currentSpeed / maxSpeed, 1);
1902
+ const baseCamDistance = v.size.l * 0.8;
1903
+ const maxCamDistanceLimit = v.size.l * 5;
1904
+ const targetDistance = THREE3.MathUtils.lerp(
1905
+ baseCamDistance,
1906
+ maxCamDistanceLimit,
1907
+ speedRatio
1908
+ );
1909
+ this._personToCam.subVectors(
1910
+ this.camera.position,
1911
+ vehicleGroup.position
1912
+ );
1913
+ const origin = vehicleGroup.position.clone().add(new THREE3.Vector3(0, 0, 0));
1914
+ const direction = this._personToCam.clone().normalize();
1915
+ const desiredDist = targetDistance;
1916
+ this._raycasterPersonToCam.set(origin, direction);
1917
+ this._raycasterPersonToCam.far = desiredDist;
1918
+ const intersects = this._raycasterPersonToCam.intersectObject(
1919
+ this.collider,
1920
+ false
1921
+ );
1922
+ if (intersects.length > 0) {
1923
+ const hit = intersects[0];
1924
+ const safeDist = Math.max(
1925
+ hit.distance - this._camEpsilon,
1926
+ this.minCamDistance
1927
+ );
1928
+ const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
1929
+ this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
1930
+ } else {
1931
+ this._raycasterPersonToCam.far = maxCamDistanceLimit;
1932
+ const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(
1933
+ this.collider,
1934
+ false
1935
+ );
1936
+ let safeDist = desiredDist;
1937
+ if (intersectsMaxDis.length) {
1938
+ const hitMax = intersectsMaxDis[0];
1939
+ safeDist = Math.min(
1940
+ desiredDist,
1941
+ hitMax.distance - this._camEpsilon
1942
+ );
1943
+ }
1944
+ const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
1945
+ this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
1946
+ }
1947
+ if ((this.fwdPressed || this.bkdPressed) && this.vehicleParams.followVehicleDirection) {
1948
+ const vel = chassisBody.linvel();
1949
+ const velHorizontal = new THREE3.Vector3(vel.x, vel.y, vel.z);
1950
+ const velSpeed = velHorizontal.length();
1951
+ if (velSpeed > 0.3) {
1952
+ const targetBehindDir = velHorizontal.clone().normalize().negate();
1953
+ this.camBehindDir.lerp(targetBehindDir, this._camCollisionLerp).normalize();
1954
+ const camHeightOffset = v.size.h;
1955
+ const targetCamPos = lookTarget.clone().add(
1956
+ this.camBehindDir.clone().multiplyScalar(desiredDist)
1957
+ ).add(new THREE3.Vector3(0, camHeightOffset, 0));
1958
+ this.camera.position.lerp(
1959
+ targetCamPos,
1960
+ this._camCollisionLerp
1961
+ );
1962
+ this.controls.update();
1963
+ }
1964
+ }
1965
+ }
1966
+ const vehicleUp = this.upVector.clone().applyQuaternion(vehicleGroup.quaternion);
1967
+ const angleWithUp = vehicleUp.angleTo(this.upVector);
1968
+ if (angleWithUp > Math.PI / 2) {
1969
+ const size = new THREE3.Vector3();
1970
+ v.vehicleBBox?.getSize(size);
1971
+ const translation2 = chassisBody.translation();
1972
+ chassisBody.setTranslation(
1973
+ new this.RAPIER.Vector3(
1974
+ translation2.x,
1975
+ translation2.y + size.y,
1976
+ translation2.z
1977
+ ),
1978
+ true
1979
+ );
1980
+ chassisBody.setRotation(
1981
+ new this.RAPIER.Quaternion(0, 0, 0, 1),
1982
+ true
1983
+ );
1984
+ chassisBody.setLinvel(new this.RAPIER.Vector3(0, 0, 0), true);
1985
+ chassisBody.setAngvel(new this.RAPIER.Vector3(0, 0, 0), true);
1986
+ }
1987
+ }
1988
+ /**
1989
+ * 更新所有车辆物理和位置
1990
+ */
1991
+ updateVehicleInertia(delta) {
1992
+ if (!this.world) return;
1993
+ this.world.timestep = delta;
1994
+ this.world.step();
1995
+ for (const v of this.vehicles) {
1996
+ const { vehicleController, chassisBody, vehicleGroup, updateWheelVisuals } = v;
1997
+ vehicleController.updateVehicle(delta);
1998
+ if (chassisBody.isSleeping()) continue;
1999
+ const velocity = chassisBody.linvel();
2000
+ const currentSpeed = Math.sqrt(
2001
+ velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z
2002
+ );
2003
+ const maxSpeed = this.vehicleParams.power.maxSpeed * v.speedMultiplier;
2004
+ if (currentSpeed > maxSpeed) {
2005
+ const s = maxSpeed / currentSpeed;
2006
+ chassisBody.setLinvel(
2007
+ new this.RAPIER.Vector3(
2008
+ velocity.x * s,
2009
+ velocity.y * s,
2010
+ velocity.z * s
2011
+ ),
2012
+ true
2013
+ );
2014
+ }
2015
+ const translation = chassisBody.translation();
2016
+ const rotationSync = chassisBody.rotation();
2017
+ vehicleGroup.position.set(
2018
+ translation.x,
2019
+ translation.y,
2020
+ translation.z
2021
+ );
2022
+ vehicleGroup.quaternion.set(
2023
+ rotationSync.x,
2024
+ rotationSync.y,
2025
+ rotationSync.z,
2026
+ rotationSync.w
2027
+ );
2028
+ if (updateWheelVisuals) updateWheelVisuals();
2029
+ }
2030
+ }
2031
+ /**
2032
+ * 设置人物缩放
2033
+ */
2034
+ setPlayerScale(newScale) {
2035
+ if (newScale <= 0) return;
2036
+ const ratio = newScale / this.playerModel.scale;
2037
+ this.playerModel.scale = newScale;
2038
+ this.gravity *= ratio;
2039
+ this.jumpHeight *= ratio;
2040
+ this.playerSpeed *= ratio;
2041
+ this.playerFlySpeed *= ratio;
2042
+ this.curPlayerSpeed *= ratio;
2043
+ this._camEpsilon *= ratio;
2044
+ this.minCamDistance *= ratio;
2045
+ this.maxCamDistance *= ratio;
2046
+ this.orginMaxCamDistance *= ratio;
2047
+ if (this.isFirstPerson) this.scene.attach(this.camera);
2048
+ this.player?.scale.multiplyScalar(ratio);
2049
+ if (this.player?.capsuleInfo) this.player.capsuleInfo.radius *= ratio;
2050
+ if (this.isFirstPerson) this.setFirstPersonCamera();
2051
+ }
2052
+ /**
2053
+ * 更新人物
2054
+ */
2055
+ updatePlayer(delta) {
2056
+ if (this.isMovingToBoardingPoint) {
2057
+ this.updateMoveToBoardingPoint(delta);
2058
+ }
760
2059
  if (!this.isFlying) {
761
2060
  this.player.position.addScaledVector(this.playerVelocity, delta);
762
2061
  }
2062
+ if (this.isBoardingAnimPlaying) {
2063
+ const action = this.personActions?.get("enterCar");
2064
+ const duration = action.getClip().duration;
2065
+ const timeScale = action.getEffectiveTimeScale();
2066
+ const remaining = (duration - action.time) / timeScale * 1e3;
2067
+ if (!this.closeDoorTriggered && remaining <= 500) {
2068
+ this.closeDoorTriggered = true;
2069
+ this.openVehicleDoor(false);
2070
+ }
2071
+ if (action.time >= duration) {
2072
+ this.isBoardingAnimPlaying = false;
2073
+ this.closeDoorTriggered = false;
2074
+ this.onEnterCarAnimFinished();
2075
+ return;
2076
+ }
2077
+ }
2078
+ if (this.isExitAnimPlaying) {
2079
+ const action = this.personActions?.get("exitCar");
2080
+ if (action) {
2081
+ const duration = action.getClip().duration;
2082
+ const timeScale = action.getEffectiveTimeScale();
2083
+ const remaining = (duration - action.time) / timeScale * 1e3;
2084
+ if (!this.closeExitDoorTriggered && remaining <= 500) {
2085
+ this.closeExitDoorTriggered = true;
2086
+ this.openVehicleDoor(false);
2087
+ }
2088
+ if (action.time >= duration) {
2089
+ this.isExitAnimPlaying = false;
2090
+ this.closeExitDoorTriggered = false;
2091
+ }
2092
+ }
2093
+ }
763
2094
  this.updateMixers(delta);
2095
+ if (this.controllerMode === 1) return;
764
2096
  this.camera.getWorldDirection(this.camDir);
765
2097
  let angle = Math.atan2(this.camDir.z, this.camDir.x) + Math.PI / 2;
766
2098
  angle = 2 * Math.PI - angle;
@@ -775,47 +2107,56 @@ var PlayerController = class {
775
2107
  if (this.spacePressed) this.moveDir.add(this.DIR_UP);
776
2108
  }
777
2109
  if (this.isFlying && this.fwdPressed) {
778
- this.playerSpeed = this.shiftPressed ? this.originPlayerSpeed * 12 : this.originPlayerSpeed * 7;
2110
+ this.curPlayerSpeed = this.shiftPressed ? this.playerFlySpeed * 2 : this.playerFlySpeed;
779
2111
  } else {
780
- this.playerSpeed = this.shiftPressed ? this.originPlayerSpeed * 2 : this.originPlayerSpeed;
2112
+ this.curPlayerSpeed = this.shiftPressed ? this.playerSpeed * 2 : this.playerSpeed;
781
2113
  }
782
2114
  this.moveDir.normalize().applyAxisAngle(this.upVector, angle);
783
- this.player.position.addScaledVector(this.moveDir, this.playerSpeed * delta);
2115
+ this.player.position.addScaledVector(
2116
+ this.moveDir,
2117
+ this.curPlayerSpeed * delta
2118
+ );
784
2119
  let playerDistanceFromGround = Infinity;
785
- this._originTmp.set(this.player.position.x, this.player.position.y, this.player.position.z);
2120
+ this._originTmp.set(
2121
+ this.player.position.x,
2122
+ this.player.position.y,
2123
+ this.player.position.z
2124
+ );
786
2125
  this._raycaster.ray.origin.copy(this._originTmp);
787
- const intersects = this._raycaster.intersectObject(this.collider, false);
2126
+ const intersects = this._raycaster.intersectObject(
2127
+ this.collider,
2128
+ false
2129
+ );
788
2130
  if (intersects.length > 0) {
789
2131
  playerDistanceFromGround = this.player.position.y - intersects[0].point.y;
790
- const normal = intersects[0].normal;
791
- const angle2 = this.getAngleWithYAxis(normal) * 180 / Math.PI;
792
2132
  const maxH = this.playerHeight * this.playerModel.scale * 0.9;
793
2133
  const h = this.playerHeight * this.playerModel.scale * 0.75;
794
2134
  const minH = this.playerHeight * this.playerModel.scale * 0.7;
795
2135
  if (!this.isFlying) {
796
- if (playerDistanceFromGround > maxH) {
2136
+ if (playerDistanceFromGround >= maxH) {
797
2137
  this.playerVelocity.y += delta * this.gravity;
798
- this.player.position.addScaledVector(this.playerVelocity, delta);
2138
+ this.player.position.addScaledVector(
2139
+ this.playerVelocity,
2140
+ delta
2141
+ );
799
2142
  this.playerIsOnGround = false;
800
- } else if (playerDistanceFromGround > h && playerDistanceFromGround < maxH) {
801
- if (angle2 >= 0 && angle2 < 5) {
802
- this.playerVelocity.y += delta * this.gravity;
803
- this.player.position.addScaledVector(this.playerVelocity, delta);
2143
+ } else if (playerDistanceFromGround >= h && playerDistanceFromGround < maxH) {
2144
+ if (!this.spacePressed) {
2145
+ this.playerVelocity.set(0, 0, 0);
804
2146
  this.playerIsOnGround = true;
805
- } else {
806
- if (this.spacePressed) {
807
- this.playerVelocity.y += delta * this.gravity;
808
- } else {
809
- this.playerVelocity.set(0, 0, 0);
810
- this.playerIsOnGround = true;
811
- }
2147
+ this.player.position.y = intersects[0].point.y + h;
812
2148
  }
813
- } else if (playerDistanceFromGround > minH && playerDistanceFromGround < h) {
2149
+ } else if (playerDistanceFromGround >= minH && playerDistanceFromGround < h) {
814
2150
  this.playerVelocity.set(0, 0, 0);
815
2151
  this.playerIsOnGround = true;
2152
+ this.player.position.y = intersects[0].point.y + h;
816
2153
  } else if (playerDistanceFromGround < minH) {
817
2154
  this.playerVelocity.set(0, 0, 0);
818
- this.player.position.set(this.player.position.x, intersects[0].point.y + h, this.player.position.z);
2155
+ this.player.position.set(
2156
+ this.player.position.x,
2157
+ intersects[0].point.y + h,
2158
+ this.player.position.z
2159
+ );
819
2160
  this.playerIsOnGround = true;
820
2161
  }
821
2162
  }
@@ -830,25 +2171,57 @@ var PlayerController = class {
830
2171
  this.tempBox.expandByPoint(this.tempSegment.start);
831
2172
  this.tempBox.expandByPoint(this.tempSegment.end);
832
2173
  this.tempBox.expandByScalar(capsuleInfo.radius);
833
- const bvh = this.collider?.geometry;
834
- bvh?.boundsTree?.shapecast({
835
- // 检测包围盒碰撞
836
- intersectsBounds: (box) => box.intersectsBox(this.tempBox),
837
- // 检测三角形碰撞
838
- intersectsTriangle: (tri) => {
839
- const triPoint = this.tempVector;
840
- const capsulePoint = this.tempVector2;
841
- const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint);
842
- if (distance < capsuleInfo.radius) {
843
- const depth = capsuleInfo.radius - distance;
844
- const direction = capsulePoint.sub(triPoint).normalize();
845
- this.tempSegment.start.addScaledVector(direction, depth);
846
- this.tempSegment.end.addScaledVector(direction, depth);
2174
+ if (!this.isMovingToBoardingPoint) {
2175
+ this.collider?.geometry?.boundsTree?.shapecast({
2176
+ intersectsBounds: (box) => box.intersectsBox(this.tempBox),
2177
+ intersectsTriangle: (tri) => {
2178
+ const triPoint = this.tempVector;
2179
+ const capsulePoint = this.tempVector2;
2180
+ const distance = tri.closestPointToSegment(
2181
+ this.tempSegment,
2182
+ triPoint,
2183
+ capsulePoint
2184
+ );
2185
+ if (distance < capsuleInfo.radius) {
2186
+ const normal = tri.getNormal(new THREE3.Vector3());
2187
+ if (normal.y > 0.5 && !this.isFlying) return;
2188
+ const depth = capsuleInfo.radius - distance;
2189
+ const direction = capsulePoint.sub(triPoint).normalize();
2190
+ this.tempSegment.start.addScaledVector(
2191
+ direction,
2192
+ depth
2193
+ );
2194
+ this.tempSegment.end.addScaledVector(direction, depth);
2195
+ }
847
2196
  }
848
- }
849
- });
2197
+ });
2198
+ this.dynamicCollider?.geometry?.boundsTree?.shapecast({
2199
+ intersectsBounds: (box) => box.intersectsBox(this.tempBox),
2200
+ intersectsTriangle: (tri) => {
2201
+ const triPoint = this.tempVector;
2202
+ const capsulePoint = this.tempVector2;
2203
+ const distance = tri.closestPointToSegment(
2204
+ this.tempSegment,
2205
+ triPoint,
2206
+ capsulePoint
2207
+ );
2208
+ if (distance < capsuleInfo.radius) {
2209
+ const depth = capsuleInfo.radius - distance;
2210
+ const direction = capsulePoint.sub(triPoint).normalize();
2211
+ this.tempSegment.start.addScaledVector(
2212
+ direction,
2213
+ depth
2214
+ );
2215
+ this.tempSegment.end.addScaledVector(direction, depth);
2216
+ }
2217
+ }
2218
+ });
2219
+ }
850
2220
  const newPosition = this.tempVector.copy(this.tempSegment.start).applyMatrix4(this.collider.matrixWorld);
851
- const deltaVector = this.tempVector2.subVectors(newPosition, this.player.position);
2221
+ const deltaVector = this.tempVector2.subVectors(
2222
+ newPosition,
2223
+ this.player.position
2224
+ );
852
2225
  const offset = Math.max(0, deltaVector.length() - 1e-5);
853
2226
  deltaVector.normalize().multiplyScalar(offset);
854
2227
  this.player.position.add(deltaVector);
@@ -865,102 +2238,178 @@ var PlayerController = class {
865
2238
  } else {
866
2239
  lookTarget = this.player.position.clone().add(this.camDir);
867
2240
  }
868
- this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
2241
+ this.targetMat.lookAt(
2242
+ this.player.position,
2243
+ lookTarget,
2244
+ this.player.up
2245
+ );
869
2246
  this.targetQuat.setFromRotationMatrix(this.targetMat);
870
2247
  const alpha = Math.min(1, this.rotationSpeed * delta);
871
2248
  this.player.quaternion.slerp(this.targetQuat, alpha);
872
2249
  }
873
2250
  if ((this.thirdMouseMode === 1 || this.thirdMouseMode === 3) && this.moveDir.lengthSq() > 0) {
874
2251
  lookTarget = this.player.position.clone().add(this.moveDir);
875
- this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
2252
+ this.targetMat.lookAt(
2253
+ this.player.position,
2254
+ lookTarget,
2255
+ this.player.up
2256
+ );
876
2257
  this.targetQuat.setFromRotationMatrix(this.targetMat);
877
2258
  const alpha = Math.min(1, this.rotationSpeed * delta);
878
2259
  this.player.quaternion.slerp(this.targetQuat, alpha);
879
2260
  }
880
2261
  }
881
2262
  if (this.isFlying) {
882
- this.camDir.y = 0;
883
- this.camDir.normalize();
884
- this.camDir.negate();
885
- this.moveDir.normalize();
886
- this.moveDir.negate();
887
- const lookTarget = this.player.position.clone().add(this.fwdPressed ? this.moveDir : this.camDir);
888
- this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
889
- this.targetQuat.setFromRotationMatrix(this.targetMat);
890
- const alpha = Math.min(1, this.rotationSpeed * delta);
891
- this.player.quaternion.slerp(this.targetQuat, alpha);
2263
+ if (!this.isFirstPerson) {
2264
+ this.camDir.y = 0;
2265
+ this.camDir.normalize();
2266
+ this.camDir.negate();
2267
+ this.moveDir.normalize();
2268
+ this.moveDir.negate();
2269
+ const lookTarget = this.player.position.clone().add(this.fwdPressed ? this.moveDir : this.camDir);
2270
+ this.targetMat.lookAt(
2271
+ this.player.position,
2272
+ lookTarget,
2273
+ this.player.up
2274
+ );
2275
+ this.targetQuat.setFromRotationMatrix(this.targetMat);
2276
+ const alpha = Math.min(1, this.rotationSpeed * delta);
2277
+ this.player.quaternion.slerp(this.targetQuat, alpha);
2278
+ }
892
2279
  }
893
2280
  if (!this.isFirstPerson) {
894
2281
  const lookTarget = this.player.position.clone();
895
- lookTarget.y += 30 * this.playerModel.scale;
2282
+ lookTarget.y += this.playerHeight / 8 * this.playerModel.scale;
896
2283
  this.camera.position.sub(this.controls.target);
897
2284
  this.controls.target.copy(lookTarget);
898
2285
  this.camera.position.add(lookTarget);
899
2286
  this.controls.update();
900
2287
  if (!this.enableZoom) {
901
- this._personToCam.subVectors(this.camera.position, this.player.position);
902
- const origin = this.player.position.clone().add(new THREE.Vector3(0, 0, 0));
2288
+ this._personToCam.subVectors(
2289
+ this.camera.position,
2290
+ this.player.position
2291
+ );
2292
+ const origin = this.player.position.clone();
903
2293
  const direction = this._personToCam.clone().normalize();
904
2294
  const desiredDist = this._personToCam.length();
905
2295
  this._raycasterPersonToCam.set(origin, direction);
906
2296
  this._raycasterPersonToCam.far = desiredDist;
907
- const intersects2 = this._raycasterPersonToCam.intersectObject(this.collider, false);
908
- if (intersects2.length > 0) {
909
- const hit = intersects2[0];
910
- const safeDist = Math.max(hit.distance - this._camEpsilon, this._minCamDistance);
2297
+ const intersectsCamera = this._raycasterPersonToCam.intersectObject(
2298
+ this.collider,
2299
+ false
2300
+ );
2301
+ if (intersectsCamera.length > 0) {
2302
+ const hit = intersectsCamera[0];
2303
+ const safeDist = Math.max(
2304
+ hit.distance - this._camEpsilon,
2305
+ this.minCamDistance
2306
+ );
911
2307
  const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
912
- this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
2308
+ this.camera.position.lerp(
2309
+ targetCamPos,
2310
+ this._camCollisionLerp
2311
+ );
913
2312
  } else {
914
- this._raycasterPersonToCam.far = this._maxCamDistance;
915
- const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(this.collider, false);
916
- let safeDist = this._maxCamDistance;
2313
+ this._raycasterPersonToCam.far = this.maxCamDistance;
2314
+ const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(
2315
+ this.collider,
2316
+ false
2317
+ );
2318
+ let safeDist = this.maxCamDistance;
917
2319
  if (intersectsMaxDis.length) {
918
2320
  const hitMax = intersectsMaxDis[0];
919
2321
  safeDist = hitMax.distance - this._camEpsilon;
920
2322
  }
921
2323
  const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
922
- this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
2324
+ this.camera.position.lerp(
2325
+ targetCamPos,
2326
+ this._camCollisionLerp
2327
+ );
923
2328
  }
924
2329
  }
925
2330
  }
926
2331
  if (this.player.position.y < this.boundingBoxMinY - 1) {
927
- this._originTmp.set(this.player.position.x, 1e4, this.player.position.z);
2332
+ this._originTmp.set(
2333
+ this.player.position.x,
2334
+ 1e4,
2335
+ this.player.position.z
2336
+ );
928
2337
  this._raycaster.ray.origin.copy(this._originTmp);
929
- const intersects2 = this._raycaster.intersectObject(this.collider, false);
930
- if (intersects2.length > 0) {
2338
+ const intersectsFall = this._raycaster.intersectObject(
2339
+ this.collider,
2340
+ false
2341
+ );
2342
+ if (intersectsFall.length > 0) {
931
2343
  console.log("\u73A9\u5BB6\u4E3Abug\u610F\u5916\u6389\u843D");
932
- this.reset(new THREE.Vector3(this.player.position.x, intersects2[0].point.y + 5, this.player.position.z));
2344
+ this.reset(
2345
+ new THREE3.Vector3(
2346
+ this.player.position.x,
2347
+ intersectsFall[0].point.y + 5,
2348
+ this.player.position.z
2349
+ )
2350
+ );
933
2351
  } else {
934
2352
  console.log("\u73A9\u5BB6\u6B63\u5E38\u6389\u843D");
935
- this.reset(new THREE.Vector3(this.player.position.x, this.player.position.y + 15, this.player.position.z));
2353
+ this.reset(
2354
+ new THREE3.Vector3(
2355
+ this.player.position.x,
2356
+ this.player.position.y + 15,
2357
+ this.player.position.z
2358
+ )
2359
+ );
2360
+ }
2361
+ }
2362
+ if (this.isShowMobileControls) {
2363
+ if (this.vehicles.length) {
2364
+ let near = false;
2365
+ for (const v of this.vehicles) {
2366
+ this.nearCheckLocal.copy(v.boardingPoint).multiplyScalar(v.scale);
2367
+ v.vehicleGroup.localToWorld(
2368
+ this.nearCheckWorld.copy(this.nearCheckLocal)
2369
+ );
2370
+ if (this.player.position.distanceTo(this.nearCheckWorld) < 800 * this.playerModel.scale) {
2371
+ near = true;
2372
+ this.syncVehicleBtnEl(near);
2373
+ break;
2374
+ }
2375
+ }
2376
+ if (near !== this.isNearVehicle) {
2377
+ this.isNearVehicle = near;
2378
+ this.syncVehicleBtnEl(near);
2379
+ }
2380
+ } else {
2381
+ this.isNearVehicle = false;
2382
+ this.syncVehicleBtnEl(false);
936
2383
  }
937
2384
  }
938
2385
  }
2386
+ /**
2387
+ * 获取屏幕中心点向前射线与碰撞体的交点
2388
+ */
2389
+ getCenterScreenRaycastHit() {
2390
+ this.camera.updateMatrixWorld();
2391
+ this.centerRay.setFromCamera(this.centerMouse, this.camera);
2392
+ const intersects = this.centerRay.intersectObject(this.collider, false);
2393
+ return intersects[0];
2394
+ }
939
2395
  /**
940
2396
  * 更新模型动画
941
2397
  */
942
2398
  updateMixers(delta) {
943
2399
  if (this.personMixer) this.personMixer.update(delta);
944
- if (this.vehicleMixer) this.vehicleMixer.update(delta);
2400
+ for (const v of this.vehicles) {
2401
+ v.vehicleMixer?.update(delta);
2402
+ }
945
2403
  }
946
- /**
947
- * 重置玩家位置
948
- */
949
2404
  reset(position) {
950
2405
  if (!this.player) return;
951
2406
  this.playerVelocity.set(0, 0, 0);
952
2407
  this.player.position.copy(position ?? this.initPos);
953
2408
  }
954
- /**
955
- * 获取玩家位置
956
- */
957
2409
  getPosition() {
958
- return this.player.position;
2410
+ return this.player?.position;
959
2411
  }
960
2412
  // ==================== 输入处理 ====================
961
- /**
962
- * 设置输入
963
- */
964
2413
  setInput(input) {
965
2414
  if (typeof input.moveX === "number") {
966
2415
  this.lftPressed = input.moveX === -1;
@@ -977,7 +2426,14 @@ var PlayerController = class {
977
2426
  }
978
2427
  if (typeof input.jump === "boolean") {
979
2428
  if (input.jump) {
2429
+ if (this.isMovingToBoardingPoint) {
2430
+ this.isMovingToBoardingPoint = false;
2431
+ this.boardingWaypoints = [];
2432
+ this.currentWaypointIndex = 0;
2433
+ this.boardingTargetDir = null;
2434
+ }
980
2435
  this.spacePressed = true;
2436
+ if (this.controllerMode == 1) return;
981
2437
  if (!this.playerIsOnGround || this.isFlying) return;
982
2438
  this.playPersonAnimationByName("jumping");
983
2439
  this.playerVelocity.y = this.jumpHeight;
@@ -992,17 +2448,21 @@ var PlayerController = class {
992
2448
  if (input.toggleView) {
993
2449
  this.changeView();
994
2450
  }
995
- if (input.toggleFly) {
2451
+ if (input.toggleFly && this.playerFlyEnabled && this.controllerMode == 0) {
996
2452
  this.isFlying = !this.isFlying;
997
2453
  this.setAnimationByPressed();
998
2454
  if (!this.isFlying && !this.playerIsOnGround) {
999
2455
  this.playPersonAnimationByName("jumping");
1000
2456
  }
1001
2457
  }
2458
+ if (input.toggleVehicle) {
2459
+ if (this.controllerMode == 0) {
2460
+ this.enterVehicle();
2461
+ } else {
2462
+ this.exitVehicle();
2463
+ }
2464
+ }
1002
2465
  }
1003
- /**
1004
- * 事件绑定
1005
- */
1006
2466
  onAllEvent() {
1007
2467
  this.isupdate = true;
1008
2468
  this.setPointerLock();
@@ -1011,9 +2471,6 @@ var PlayerController = class {
1011
2471
  window.addEventListener("mousemove", this._mouseMove);
1012
2472
  window.addEventListener("click", this._mouseClick);
1013
2473
  }
1014
- /**
1015
- * 事件解绑
1016
- */
1017
2474
  offAllEvent() {
1018
2475
  this.isupdate = false;
1019
2476
  document.exitPointerLock();
@@ -1022,9 +2479,6 @@ var PlayerController = class {
1022
2479
  window.removeEventListener("mousemove", this._mouseMove);
1023
2480
  window.removeEventListener("click", this._mouseClick);
1024
2481
  }
1025
- /**
1026
- * 初始化移动端摇杆控制
1027
- */
1028
2482
  async initMobileControls() {
1029
2483
  this.controls.maxPolarAngle = Math.PI * (300 / 360);
1030
2484
  this.controls.touches = { ONE: null, TWO: null };
@@ -1047,11 +2501,17 @@ var PlayerController = class {
1047
2501
  userSelect: "none"
1048
2502
  });
1049
2503
  container.appendChild(this.joystickZoneEl);
1050
- ["touchstart", "touchmove", "touchend", "touchcancel"].forEach((evtName) => {
1051
- this.joystickZoneEl?.addEventListener(evtName, (e) => e.preventDefault(), {
1052
- passive: false
1053
- });
1054
- });
2504
+ ["touchstart", "touchmove", "touchend", "touchcancel"].forEach(
2505
+ (evtName) => {
2506
+ this.joystickZoneEl?.addEventListener(
2507
+ evtName,
2508
+ (e) => e.preventDefault(),
2509
+ {
2510
+ passive: false
2511
+ }
2512
+ );
2513
+ }
2514
+ );
1055
2515
  this.joystickManager = nipple.create({
1056
2516
  zone: this.joystickZoneEl,
1057
2517
  mode: "static",
@@ -1074,15 +2534,22 @@ var PlayerController = class {
1074
2534
  const dirY = rawY > deadzone ? 1 : rawY < -deadzone ? -1 : 0;
1075
2535
  const sprintThreshold = JOY_SIZE / 2;
1076
2536
  const isSprinting = distance >= sprintThreshold;
1077
- const prev = this.prevJoyState || { dirX: 0, dirY: 0, shift: false };
1078
- if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift) {
2537
+ const prev = this.prevJoyState || {
2538
+ dirX: 0,
2539
+ dirY: 0,
2540
+ shift: false
2541
+ };
2542
+ if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift)
1079
2543
  return;
1080
- }
1081
2544
  this.prevJoyState = { dirX, dirY, shift: isSprinting };
1082
2545
  this.setInput({ moveX: dirX, moveY: dirY, shift: isSprinting });
1083
2546
  });
1084
2547
  this.joystickManager.on("end", () => {
1085
- const prev = this.prevJoyState || { dirX: 0, dirY: 0, shift: false };
2548
+ const prev = this.prevJoyState || {
2549
+ dirX: 0,
2550
+ dirY: 0,
2551
+ shift: false
2552
+ };
1086
2553
  if (prev.dirX !== 0 || prev.dirY !== 0 || prev.shift !== false) {
1087
2554
  this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
1088
2555
  this.setInput({ moveX: 0, moveY: 0, shift: false });
@@ -1101,15 +2568,29 @@ var PlayerController = class {
1101
2568
  userSelect: "none"
1102
2569
  });
1103
2570
  container.appendChild(this.lookAreaEl);
1104
- ["touchstart", "touchmove", "touchend", "touchcancel"].forEach((evtName) => {
1105
- this.lookAreaEl?.addEventListener(evtName, (e) => e.preventDefault(), {
1106
- passive: false
1107
- });
2571
+ ["touchstart", "touchmove", "touchend", "touchcancel"].forEach(
2572
+ (evtName) => {
2573
+ this.lookAreaEl?.addEventListener(
2574
+ evtName,
2575
+ (e) => e.preventDefault(),
2576
+ {
2577
+ passive: false
2578
+ }
2579
+ );
2580
+ }
2581
+ );
2582
+ this.lookAreaEl.addEventListener("pointerdown", this.onPointerDown, {
2583
+ passive: false
2584
+ });
2585
+ this.lookAreaEl.addEventListener("pointermove", this.onPointerMove, {
2586
+ passive: false
2587
+ });
2588
+ this.lookAreaEl.addEventListener("pointerup", this.onPointerUp, {
2589
+ passive: false
2590
+ });
2591
+ this.lookAreaEl.addEventListener("pointercancel", this.onPointerUp, {
2592
+ passive: false
1108
2593
  });
1109
- this.lookAreaEl.addEventListener("pointerdown", this.onPointerDown, { passive: false });
1110
- this.lookAreaEl.addEventListener("pointermove", this.onPointerMove, { passive: false });
1111
- this.lookAreaEl.addEventListener("pointerup", this.onPointerUp, { passive: false });
1112
- this.lookAreaEl.addEventListener("pointercancel", this.onPointerUp, { passive: false });
1113
2594
  const createBtn = (rightPx, bottomPx, bgUrl) => {
1114
2595
  const btn = document.createElement("button");
1115
2596
  const styles = {
@@ -1141,7 +2622,9 @@ var PlayerController = class {
1141
2622
  Object.assign(btn.style, styles);
1142
2623
  container.appendChild(btn);
1143
2624
  ["touchstart", "touchend", "touchcancel"].forEach((evtName) => {
1144
- btn.addEventListener(evtName, (e) => e.preventDefault(), { passive: false });
2625
+ btn.addEventListener(evtName, (e) => e.preventDefault(), {
2626
+ passive: false
2627
+ });
1145
2628
  });
1146
2629
  return btn;
1147
2630
  };
@@ -1188,10 +2671,16 @@ var PlayerController = class {
1188
2671
  },
1189
2672
  { passive: false }
1190
2673
  );
2674
+ this.vehicleBtnEl = createBtn(14 + 100, 14 + 120, vehicle_default);
2675
+ this.vehicleBtnEl.addEventListener(
2676
+ "touchstart",
2677
+ (e) => {
2678
+ e.preventDefault();
2679
+ this.setInput({ toggleVehicle: true });
2680
+ },
2681
+ { passive: false }
2682
+ );
1191
2683
  }
1192
- /**
1193
- * 销毁移动端摇杆控制
1194
- */
1195
2684
  destroyMobileControls() {
1196
2685
  try {
1197
2686
  if (this.joystickManager && this.joystickManager.destroy) {
@@ -1199,7 +2688,9 @@ var PlayerController = class {
1199
2688
  this.joystickManager = null;
1200
2689
  }
1201
2690
  if (this.joystickZoneEl?.parentElement) {
1202
- this.joystickZoneEl.parentElement.removeChild(this.joystickZoneEl);
2691
+ this.joystickZoneEl.parentElement.removeChild(
2692
+ this.joystickZoneEl
2693
+ );
1203
2694
  this.joystickZoneEl = null;
1204
2695
  }
1205
2696
  if (this.lookAreaEl?.parentElement) {
@@ -1218,18 +2709,87 @@ var PlayerController = class {
1218
2709
  this.viewBtnEl.parentElement.removeChild(this.viewBtnEl);
1219
2710
  this.viewBtnEl = null;
1220
2711
  }
1221
- this.lookAreaEl?.removeEventListener("pointerdown", this.onPointerDown);
1222
- this.lookAreaEl?.removeEventListener("pointermove", this.onPointerMove);
2712
+ if (this.vehicleBtnEl?.parentElement) {
2713
+ this.vehicleBtnEl.parentElement.removeChild(this.vehicleBtnEl);
2714
+ this.vehicleBtnEl = null;
2715
+ }
2716
+ this.lookAreaEl?.removeEventListener(
2717
+ "pointerdown",
2718
+ this.onPointerDown
2719
+ );
2720
+ this.lookAreaEl?.removeEventListener(
2721
+ "pointermove",
2722
+ this.onPointerMove
2723
+ );
1223
2724
  this.lookAreaEl?.removeEventListener("pointerup", this.onPointerUp);
1224
- this.lookAreaEl?.removeEventListener("pointercancel", this.onPointerUp);
2725
+ this.lookAreaEl?.removeEventListener(
2726
+ "pointercancel",
2727
+ this.onPointerUp
2728
+ );
1225
2729
  } catch (e) {
1226
2730
  console.warn("\u9500\u6BC1\u79FB\u52A8\u7AEF\u6447\u6746\u63A7\u5236\u65F6\u51FA\u9519\uFF1A", e);
1227
2731
  }
1228
2732
  }
2733
+ syncVehicleBtnEl(show) {
2734
+ if (!this.vehicleBtnEl) return;
2735
+ this.vehicleBtnEl.style.display = show ? "block" : "none";
2736
+ }
2737
+ syncControllerModeBtnEl() {
2738
+ if (!this.isShowMobileControls) return;
2739
+ if (this.controllerMode == 0) {
2740
+ this.flyBtnEl.style.display = "block";
2741
+ const overlayColor = "rgba(0,0,0,0.5)";
2742
+ this.jumpBtnEl.style.backgroundImage = `linear-gradient(${overlayColor}, ${overlayColor}), url("${jump_default}")`;
2743
+ } else {
2744
+ this.flyBtnEl.style.display = "none";
2745
+ const overlayColor = "rgba(0,0,0,0.5)";
2746
+ this.jumpBtnEl.style.backgroundImage = `linear-gradient(${overlayColor}, ${overlayColor}), url("${break_default}")`;
2747
+ this.jumpBtnEl.style.backgroundImage = `url(${break_default})`;
2748
+ }
2749
+ }
2750
+ // ==================== 更新参数 ====================
2751
+ setMouseSensitivity(mouseSensity) {
2752
+ this.mouseSensity = mouseSensity;
2753
+ this.controls.rotateSpeed = this.mouseSensity * 0.05;
2754
+ }
2755
+ setGravity(gravity) {
2756
+ this.gravity = gravity * this.playerModel.scale;
2757
+ }
2758
+ setJumpHeight(jumpHeight) {
2759
+ this.jumpHeight = jumpHeight * this.playerModel.scale;
2760
+ }
2761
+ setPlayerSpeed(playerSpeed) {
2762
+ this.playerSpeed = playerSpeed * this.playerModel.scale;
2763
+ this.curPlayerSpeed = this.playerSpeed;
2764
+ }
2765
+ setPlayerFlySpeed(playerFlySpeed) {
2766
+ this.playerFlySpeed = playerFlySpeed * this.playerModel.scale;
2767
+ }
2768
+ setMinCamDistance(minCamDistance) {
2769
+ this.minCamDistance = minCamDistance * this.playerModel.scale;
2770
+ }
2771
+ setMaxCamDistance(maxCamDistance) {
2772
+ this.maxCamDistance = maxCamDistance * this.playerModel.scale;
2773
+ this.orginMaxCamDistance = this.maxCamDistance;
2774
+ }
2775
+ setThirdMouseMode(thirdMouseMode) {
2776
+ this.thirdMouseMode = thirdMouseMode;
2777
+ this.setPointerLock();
2778
+ }
2779
+ setEnableZoom(enableZoom) {
2780
+ this.enableZoom = enableZoom;
2781
+ this.controls.enableZoom = this.enableZoom;
2782
+ }
2783
+ setDebug(debug) {
2784
+ if (this.collider) this.scene.remove(this.collider);
2785
+ if (debug) {
2786
+ this.scene.add(this.collider);
2787
+ this.player.material.opacity = 0.5;
2788
+ } else {
2789
+ this.player.material.opacity = 0;
2790
+ }
2791
+ }
1229
2792
  // ==================== 销毁 ====================
1230
- /**
1231
- * 销毁人物控制器
1232
- */
1233
2793
  destroy() {
1234
2794
  this.offAllEvent();
1235
2795
  if (this.player) {
@@ -1251,6 +2811,12 @@ var PlayerController = class {
1251
2811
  this.collider = null;
1252
2812
  }
1253
2813
  this.destroyMobileControls();
2814
+ for (const v of this.vehicles) {
2815
+ this.scene.remove(v.vehicleGroup);
2816
+ v.pathPlanner?.dispose();
2817
+ }
2818
+ this.vehicles = [];
2819
+ this.activeVehicle = null;
1254
2820
  controllerInstance = null;
1255
2821
  }
1256
2822
  };
@@ -1259,13 +2825,30 @@ function playerController() {
1259
2825
  const c = controllerInstance;
1260
2826
  return {
1261
2827
  init: (opts, callback) => c.init(opts, callback),
2828
+ loadVehicleModel: (params) => c.loadVehicleModel(params),
1262
2829
  changeView: () => c.changeView(),
1263
2830
  reset: (pos) => c.reset(pos),
1264
2831
  update: (dt) => c.update(dt),
1265
2832
  destroy: () => c.destroy(),
1266
2833
  setInput: (i) => c.setInput(i),
1267
- getposition: () => c.getPosition(),
1268
- loadVehicleModel: (params) => c.loadVehicleModel(params)
2834
+ getPosition: () => c.getPosition(),
2835
+ getCenterScreenRaycastHit: () => c.getCenterScreenRaycastHit(),
2836
+ getPerson: () => c.person,
2837
+ getActiveVehicle: () => c.activeVehicle,
2838
+ getAllVehicles: () => c.vehicles,
2839
+ switchPlayerModel: (model) => c.switchPlayerModel(model),
2840
+ setMouseSensitivity: (mouseSensity) => c.setMouseSensitivity(mouseSensity),
2841
+ setGravity: (gravity) => c.setGravity(gravity),
2842
+ setJumpHeight: (jumpHeight) => c.setJumpHeight(jumpHeight),
2843
+ setPlayerSpeed: (playerSpeed) => c.setPlayerSpeed(playerSpeed),
2844
+ setPlayerFlySpeed: (playerFlySpeed) => c.setPlayerFlySpeed(playerFlySpeed),
2845
+ setMinCamDistance: (minCamDistance) => c.setMinCamDistance(minCamDistance),
2846
+ setMaxCamDistance: (maxCamDistance) => c.setMaxCamDistance(maxCamDistance),
2847
+ setThirdMouseMode: (thirdMouseMode) => c.setThirdMouseMode(thirdMouseMode),
2848
+ setEnableZoom: (enableZoom) => c.setEnableZoom(enableZoom),
2849
+ setDebug: (debug) => c.setDebug(debug),
2850
+ setOverShoulderView: (enable) => c.setOverShoulderView(enable),
2851
+ setPlayerScale: (scale) => c.setPlayerScale(scale)
1269
2852
  };
1270
2853
  }
1271
2854
  function onAllEvent() {