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