three-player-controller 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -1
- package/dist/index.d.mts +102 -12
- package/dist/index.d.ts +102 -12
- package/dist/index.js +6293 -404
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1247 -312
- package/dist/index.mjs.map +1 -1
- package/dist/rapier.es-XQHNYU2P.mjs +4966 -0
- package/dist/rapier.es-XQHNYU2P.mjs.map +1 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/playerController.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as THREE3 from "three";
|
|
3
3
|
import { acceleratedRaycast, MeshBVH, MeshBVHHelper } from "three-mesh-bvh";
|
|
4
4
|
import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js";
|
|
5
5
|
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
|
|
@@ -15,17 +15,287 @@ var jump_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAA
|
|
|
15
15
|
// assets/imgs/view.png
|
|
16
16
|
var view_default = "data:image/png;base64,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";
|
|
17
17
|
|
|
18
|
+
// src/utils/pathPlanner.ts
|
|
19
|
+
import * as THREE from "three";
|
|
20
|
+
var PathNode = class {
|
|
21
|
+
constructor(position) {
|
|
22
|
+
this.g = Infinity;
|
|
23
|
+
// 实际代价
|
|
24
|
+
this.h = 0;
|
|
25
|
+
// 估计代价
|
|
26
|
+
this.f = Infinity;
|
|
27
|
+
// f = g + h
|
|
28
|
+
this.parent = null;
|
|
29
|
+
this.position = position.clone();
|
|
30
|
+
}
|
|
31
|
+
equals(other) {
|
|
32
|
+
return this.position.distanceTo(other.position) < 0.01;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var PriorityQueue = class {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.elements = [];
|
|
38
|
+
}
|
|
39
|
+
enqueue(item, priority) {
|
|
40
|
+
this.elements.push({ priority, item });
|
|
41
|
+
this.elements.sort((a, b) => a.priority - b.priority);
|
|
42
|
+
}
|
|
43
|
+
dequeue() {
|
|
44
|
+
return this.elements.shift()?.item;
|
|
45
|
+
}
|
|
46
|
+
isEmpty() {
|
|
47
|
+
return this.elements.length === 0;
|
|
48
|
+
}
|
|
49
|
+
contains(item, compareFn) {
|
|
50
|
+
return this.elements.some((e) => compareFn(e.item, item));
|
|
51
|
+
}
|
|
52
|
+
update(item, newPriority, compareFn) {
|
|
53
|
+
const index = this.elements.findIndex((e) => compareFn(e.item, item));
|
|
54
|
+
if (index !== -1) {
|
|
55
|
+
this.elements[index].priority = newPriority;
|
|
56
|
+
this.elements.sort((a, b) => a.priority - b.priority);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var PathPlanner = class {
|
|
61
|
+
constructor(obstacleChecker, config = {}) {
|
|
62
|
+
this.debugLines = [];
|
|
63
|
+
this.debugPoints = [];
|
|
64
|
+
this.obstacleChecker = obstacleChecker;
|
|
65
|
+
this.config = {
|
|
66
|
+
debugEnabled: false,
|
|
67
|
+
scale: 1,
|
|
68
|
+
...config
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// 计算启发式距离
|
|
72
|
+
heuristic(a, b) {
|
|
73
|
+
return a.distanceTo(b);
|
|
74
|
+
}
|
|
75
|
+
// A*路径规划算法
|
|
76
|
+
findPath(start, goal) {
|
|
77
|
+
const startTime = performance.now();
|
|
78
|
+
if (!this.obstacleChecker.isBlocked(start, goal)) {
|
|
79
|
+
return [goal];
|
|
80
|
+
}
|
|
81
|
+
const navigationPoints = this.obstacleChecker.getNavigationNodes(start, goal);
|
|
82
|
+
const allNodes = [new PathNode(start), new PathNode(goal), ...navigationPoints.map((p) => new PathNode(p))];
|
|
83
|
+
if (allNodes.length < 2) {
|
|
84
|
+
console.warn("\u5BFC\u822A\u8282\u70B9\u4E0D\u8DB3\uFF0C\u8FD4\u56DE\u76F4\u7EBF\u8DEF\u5F84");
|
|
85
|
+
return [goal];
|
|
86
|
+
}
|
|
87
|
+
const startNode = allNodes[0];
|
|
88
|
+
const goalNode = allNodes[1];
|
|
89
|
+
startNode.g = 0;
|
|
90
|
+
startNode.h = this.heuristic(startNode.position, goalNode.position);
|
|
91
|
+
startNode.f = startNode.h;
|
|
92
|
+
const openList = new PriorityQueue();
|
|
93
|
+
const closedSet = /* @__PURE__ */ new Set();
|
|
94
|
+
openList.enqueue(startNode, startNode.f);
|
|
95
|
+
const nodeEquals = (a, b) => a.equals(b);
|
|
96
|
+
while (!openList.isEmpty()) {
|
|
97
|
+
const current = openList.dequeue();
|
|
98
|
+
if (!current) break;
|
|
99
|
+
if (current.equals(goalNode)) {
|
|
100
|
+
const path = this.reconstructPath(current);
|
|
101
|
+
const endTime = performance.now();
|
|
102
|
+
if (this.config.debugEnabled) {
|
|
103
|
+
this.visualizePath([start, ...path]);
|
|
104
|
+
}
|
|
105
|
+
return path;
|
|
106
|
+
}
|
|
107
|
+
closedSet.add(current);
|
|
108
|
+
for (const neighbor of allNodes) {
|
|
109
|
+
if (closedSet.has(neighbor)) continue;
|
|
110
|
+
if (this.obstacleChecker.isBlocked(current.position, neighbor.position)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const tentativeG = current.g + current.position.distanceTo(neighbor.position);
|
|
114
|
+
if (tentativeG < neighbor.g) {
|
|
115
|
+
neighbor.parent = current;
|
|
116
|
+
neighbor.g = tentativeG;
|
|
117
|
+
neighbor.h = this.heuristic(neighbor.position, goalNode.position);
|
|
118
|
+
neighbor.f = neighbor.g + neighbor.h;
|
|
119
|
+
if (openList.contains(neighbor, nodeEquals)) {
|
|
120
|
+
openList.update(neighbor, neighbor.f, nodeEquals);
|
|
121
|
+
} else {
|
|
122
|
+
openList.enqueue(neighbor, neighbor.f);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.warn("A*\u672A\u627E\u5230\u8DEF\u5F84\uFF0C\u4F7F\u7528\u76F4\u7EBF\u8DEF\u5F84");
|
|
128
|
+
return [goal];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 重建路径
|
|
132
|
+
*/
|
|
133
|
+
reconstructPath(endNode) {
|
|
134
|
+
const path = [];
|
|
135
|
+
let current = endNode;
|
|
136
|
+
while (current !== null) {
|
|
137
|
+
path.unshift(current.position.clone());
|
|
138
|
+
current = current.parent;
|
|
139
|
+
}
|
|
140
|
+
if (path.length > 0) {
|
|
141
|
+
path.shift();
|
|
142
|
+
}
|
|
143
|
+
return this.smoothPath(path);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 路径平滑
|
|
147
|
+
*/
|
|
148
|
+
smoothPath(path) {
|
|
149
|
+
if (path.length <= 2) return path;
|
|
150
|
+
const smoothed = [path[0]];
|
|
151
|
+
let current = 0;
|
|
152
|
+
while (current < path.length - 1) {
|
|
153
|
+
let farthest = current + 1;
|
|
154
|
+
for (let i = path.length - 1; i > current + 1; i--) {
|
|
155
|
+
if (!this.obstacleChecker.isBlocked(path[current], path[i])) {
|
|
156
|
+
farthest = i;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
smoothed.push(path[farthest]);
|
|
161
|
+
current = farthest;
|
|
162
|
+
}
|
|
163
|
+
return smoothed;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 可视化路径
|
|
167
|
+
*/
|
|
168
|
+
visualizePath(path) {
|
|
169
|
+
if (!this.config.scene || !this.config.debugEnabled) return;
|
|
170
|
+
this.clearVisualization();
|
|
171
|
+
const scale = this.config.scale || 1;
|
|
172
|
+
if (path.length > 1) {
|
|
173
|
+
const points = path.map((p) => p.clone());
|
|
174
|
+
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
175
|
+
const material = new THREE.LineBasicMaterial({
|
|
176
|
+
color: 65280,
|
|
177
|
+
linewidth: 3
|
|
178
|
+
});
|
|
179
|
+
const line = new THREE.Line(geometry, material);
|
|
180
|
+
this.config.scene.add(line);
|
|
181
|
+
this.debugLines.push(line);
|
|
182
|
+
}
|
|
183
|
+
path.forEach((point, index) => {
|
|
184
|
+
const geometry = new THREE.SphereGeometry(20 * scale);
|
|
185
|
+
const material = new THREE.MeshBasicMaterial({
|
|
186
|
+
color: index === path.length - 1 ? 16711680 : 65280
|
|
187
|
+
});
|
|
188
|
+
const sphere = new THREE.Mesh(geometry, material);
|
|
189
|
+
sphere.position.copy(point);
|
|
190
|
+
this.config.scene.add(sphere);
|
|
191
|
+
this.debugPoints.push(sphere);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 清除路径可视化
|
|
196
|
+
*/
|
|
197
|
+
clearVisualization() {
|
|
198
|
+
if (!this.config.scene) return;
|
|
199
|
+
this.debugLines.forEach((line) => {
|
|
200
|
+
this.config.scene.remove(line);
|
|
201
|
+
line.geometry.dispose();
|
|
202
|
+
line.material.dispose();
|
|
203
|
+
});
|
|
204
|
+
this.debugLines = [];
|
|
205
|
+
this.debugPoints.forEach((point) => {
|
|
206
|
+
this.config.scene.remove(point);
|
|
207
|
+
point.geometry.dispose();
|
|
208
|
+
point.material.dispose();
|
|
209
|
+
});
|
|
210
|
+
this.debugPoints = [];
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 更新配置
|
|
214
|
+
*/
|
|
215
|
+
updateConfig(config) {
|
|
216
|
+
this.config = { ...this.config, ...config };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 销毁
|
|
220
|
+
*/
|
|
221
|
+
dispose() {
|
|
222
|
+
this.clearVisualization();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// src/utils/useVehicleController.ts
|
|
227
|
+
import * as THREE2 from "three";
|
|
228
|
+
function createVehicleController(world, chassisBody, wheels, wheelsInfo) {
|
|
229
|
+
if (!world || !chassisBody) return { vehicle: null, updateWheelVisuals: () => {
|
|
230
|
+
} };
|
|
231
|
+
const vehicle = world.createVehicleController(chassisBody);
|
|
232
|
+
const suspensionDirection = new THREE2.Vector3(0, -1, 0);
|
|
233
|
+
wheelsInfo.forEach((wheel, index) => {
|
|
234
|
+
vehicle.addWheel(wheel.position, suspensionDirection, wheel.axleCs, wheel.suspensionRestLength, wheel.radius);
|
|
235
|
+
vehicle.setWheelChassisConnectionPointCs(index, wheel.position);
|
|
236
|
+
vehicle.setWheelDirectionCs(index, suspensionDirection);
|
|
237
|
+
vehicle.setWheelAxleCs(index, wheel.axleCs);
|
|
238
|
+
vehicle.setWheelSuspensionRestLength(index, wheel.suspensionRestLength);
|
|
239
|
+
vehicle.setWheelRadius(index, wheel.radius);
|
|
240
|
+
vehicle.setWheelMaxSuspensionTravel(index, wheel.suspensionRestLength * 1);
|
|
241
|
+
vehicle.setWheelSuspensionStiffness(index, 250);
|
|
242
|
+
vehicle.setWheelSuspensionCompression(index, 6);
|
|
243
|
+
vehicle.setWheelSuspensionRelaxation(index, 6);
|
|
244
|
+
vehicle.setWheelMaxSuspensionForce(index, 1e4);
|
|
245
|
+
vehicle.setWheelBrake(index, 0);
|
|
246
|
+
vehicle.setWheelSteering(index, 0);
|
|
247
|
+
vehicle.setWheelEngineForce(index, 0);
|
|
248
|
+
vehicle.setWheelFrictionSlip(index, 20);
|
|
249
|
+
vehicle.setWheelSideFrictionStiffness(index, 2);
|
|
250
|
+
});
|
|
251
|
+
const up = new THREE2.Vector3(0, 1, 0);
|
|
252
|
+
const _wheelSteeringQuat = new THREE2.Quaternion();
|
|
253
|
+
const _wheelRotationQuat = new THREE2.Quaternion();
|
|
254
|
+
function updateWheelVisuals() {
|
|
255
|
+
for (const [index, wheelObj] of wheels.entries()) {
|
|
256
|
+
if (!wheelObj) continue;
|
|
257
|
+
try {
|
|
258
|
+
const wheelAxleCs = vehicle.wheelAxleCs(index) ?? new THREE2.Vector3(1, 0, 0);
|
|
259
|
+
const connection = vehicle.wheelChassisConnectionPointCs(index)?.y ?? 0;
|
|
260
|
+
const suspension = vehicle.wheelSuspensionLength(index) ?? 0;
|
|
261
|
+
const steering = vehicle.wheelSteering(index) ?? 0;
|
|
262
|
+
const rotationRad = vehicle.wheelRotation(index) ?? 0;
|
|
263
|
+
wheelObj.position.y = connection - suspension;
|
|
264
|
+
_wheelSteeringQuat.setFromAxisAngle(up, steering);
|
|
265
|
+
_wheelRotationQuat.setFromAxisAngle(wheelAxleCs, rotationRad);
|
|
266
|
+
wheelObj.quaternion.copy(_wheelSteeringQuat).multiply(_wheelRotationQuat);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function destroy() {
|
|
272
|
+
try {
|
|
273
|
+
world.removeVehicleController(vehicle);
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
vehicle,
|
|
279
|
+
updateWheelVisuals,
|
|
280
|
+
destroy
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
18
284
|
// src/playerController.ts
|
|
19
|
-
|
|
285
|
+
THREE3.Mesh.prototype.raycast = acceleratedRaycast;
|
|
20
286
|
var controllerInstance = null;
|
|
21
|
-
var clock = new
|
|
287
|
+
var clock = new THREE3.Clock();
|
|
22
288
|
var PlayerController = class {
|
|
23
289
|
constructor() {
|
|
24
290
|
// ==================== 基本配置与参数 ====================
|
|
25
291
|
this.loader = new GLTFLoader();
|
|
292
|
+
this.controllerMode = 0;
|
|
293
|
+
// 0: 人物 1: 车辆
|
|
294
|
+
this.isChangeControllerTransitionTimer = null;
|
|
26
295
|
// ==================== 玩家基本属性 ====================
|
|
27
296
|
this.playerRadius = 45;
|
|
28
297
|
this.playerHeight = 180;
|
|
298
|
+
// 玩家参考身高
|
|
29
299
|
this.isFirstPerson = false;
|
|
30
300
|
this.boundingBoxMinY = 0;
|
|
31
301
|
// ==================== 测试参数 ====================
|
|
@@ -36,7 +306,59 @@ var PlayerController = class {
|
|
|
36
306
|
this.collider = null;
|
|
37
307
|
this.visualizer = null;
|
|
38
308
|
this.person = null;
|
|
39
|
-
this.
|
|
309
|
+
this.personHead = null;
|
|
310
|
+
this.collected = [];
|
|
311
|
+
this.dynamicCollider = null;
|
|
312
|
+
this.dynamicCollected = [];
|
|
313
|
+
// ==================== 多车辆相关 ====================
|
|
314
|
+
this.vehicles = [];
|
|
315
|
+
// 所有已加载车辆
|
|
316
|
+
this.activeVehicle = null;
|
|
317
|
+
// 当前驾驶/交互的车辆
|
|
318
|
+
this.vehicleLength = 6;
|
|
319
|
+
// 车辆参考长度
|
|
320
|
+
this.wheelSteeringQuat = new THREE3.Quaternion();
|
|
321
|
+
this.wheelRotationQuat = new THREE3.Quaternion();
|
|
322
|
+
this.RAPIER = null;
|
|
323
|
+
this.world = null;
|
|
324
|
+
// 全局车辆共享参数
|
|
325
|
+
this.vehicleParams = {
|
|
326
|
+
debug: {
|
|
327
|
+
showPhysicsBox: false
|
|
328
|
+
},
|
|
329
|
+
chassis: {
|
|
330
|
+
linearDamping: 0.5,
|
|
331
|
+
angularDamping: 0.5
|
|
332
|
+
},
|
|
333
|
+
model: {
|
|
334
|
+
rotation: -Math.PI / 2
|
|
335
|
+
},
|
|
336
|
+
power: {
|
|
337
|
+
accelerateForce: 50,
|
|
338
|
+
// 推进
|
|
339
|
+
brakeForce: 200,
|
|
340
|
+
// 刹车
|
|
341
|
+
maxSpeed: 1e4
|
|
342
|
+
// 最大速度
|
|
343
|
+
},
|
|
344
|
+
steering: {
|
|
345
|
+
maxSteerAngle: Math.PI / 4,
|
|
346
|
+
steerSpeed: 0.5,
|
|
347
|
+
steerReturnSpeed: 1
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
this.camBehindDir = new THREE3.Vector3(0, 0, 1);
|
|
351
|
+
// ==================== 上车相关 ====================
|
|
352
|
+
this.isMovingToBoardingPoint = false;
|
|
353
|
+
this.boardingWaypoints = [];
|
|
354
|
+
this.currentWaypointIndex = 0;
|
|
355
|
+
this.boardingTargetDir = null;
|
|
356
|
+
this.boardingMoveSpeed = 300;
|
|
357
|
+
this.boardingRotateSpeed = 10;
|
|
358
|
+
this.flip180Quat = new THREE3.Quaternion().setFromAxisAngle(new THREE3.Vector3(0, 1, 0), Math.PI);
|
|
359
|
+
this.enterVehicleTimer = null;
|
|
360
|
+
this.closeVehicleDoorTimer = null;
|
|
361
|
+
this.boardingPointWorld = null;
|
|
40
362
|
// ==================== 状态开关 ====================
|
|
41
363
|
this.playerIsOnGround = false;
|
|
42
364
|
this.isupdate = true;
|
|
@@ -64,46 +386,63 @@ var PlayerController = class {
|
|
|
64
386
|
this.lastTouchY = 0;
|
|
65
387
|
// ==================== 第三人称相机参数 ====================
|
|
66
388
|
this._camCollisionLerp = 0.18;
|
|
67
|
-
// 平滑系数
|
|
68
389
|
this._camEpsilon = 0.35;
|
|
69
|
-
// 摄像机与障碍物之间的安全距离
|
|
70
390
|
this._minCamDistance = 1;
|
|
71
|
-
// 摄像机最小距离
|
|
72
391
|
this._maxCamDistance = 4.4;
|
|
73
|
-
// 摄像机最大距离
|
|
74
392
|
this.orginMaxCamDistance = 4.4;
|
|
75
393
|
// ==================== 物理/运动 ====================
|
|
76
|
-
this.playerVelocity = new
|
|
77
|
-
|
|
78
|
-
this.upVector = new THREE.Vector3(0, 1, 0);
|
|
394
|
+
this.playerVelocity = new THREE3.Vector3();
|
|
395
|
+
this.upVector = new THREE3.Vector3(0, 1, 0);
|
|
79
396
|
// ==================== 临时复用向量/矩阵 ====================
|
|
80
|
-
this.tempVector = new
|
|
81
|
-
this.tempVector2 = new
|
|
82
|
-
this.tempBox = new
|
|
83
|
-
this.tempMat = new
|
|
84
|
-
this.tempSegment = new
|
|
397
|
+
this.tempVector = new THREE3.Vector3();
|
|
398
|
+
this.tempVector2 = new THREE3.Vector3();
|
|
399
|
+
this.tempBox = new THREE3.Box3();
|
|
400
|
+
this.tempMat = new THREE3.Matrix4();
|
|
401
|
+
this.tempSegment = new THREE3.Line3();
|
|
85
402
|
this.recheckAnimTimer = null;
|
|
86
403
|
// ==================== 相机朝向/移动复用向量 ====================
|
|
87
|
-
this.camDir = new
|
|
88
|
-
this.moveDir = new
|
|
89
|
-
this.targetQuat = new
|
|
90
|
-
this.targetMat = new
|
|
404
|
+
this.camDir = new THREE3.Vector3();
|
|
405
|
+
this.moveDir = new THREE3.Vector3();
|
|
406
|
+
this.targetQuat = new THREE3.Quaternion();
|
|
407
|
+
this.targetMat = new THREE3.Matrix4();
|
|
91
408
|
this.rotationSpeed = 10;
|
|
92
|
-
this.DIR_FWD = new
|
|
93
|
-
this.DIR_BKD = new
|
|
94
|
-
this.DIR_LFT = new
|
|
95
|
-
this.DIR_RGT = new
|
|
96
|
-
this.DIR_UP = new
|
|
409
|
+
this.DIR_FWD = new THREE3.Vector3(0, 0, -1);
|
|
410
|
+
this.DIR_BKD = new THREE3.Vector3(0, 0, 1);
|
|
411
|
+
this.DIR_LFT = new THREE3.Vector3(-1, 0, 0);
|
|
412
|
+
this.DIR_RGT = new THREE3.Vector3(1, 0, 0);
|
|
413
|
+
this.DIR_UP = new THREE3.Vector3(0, 1, 0);
|
|
97
414
|
// ==================== 射线检测 ====================
|
|
98
|
-
this._personToCam = new
|
|
99
|
-
this._originTmp = new
|
|
100
|
-
this._raycaster = new
|
|
101
|
-
this._raycasterPersonToCam = new
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
415
|
+
this._personToCam = new THREE3.Vector3();
|
|
416
|
+
this._originTmp = new THREE3.Vector3();
|
|
417
|
+
this._raycaster = new THREE3.Raycaster(new THREE3.Vector3(), new THREE3.Vector3(0, -1, 0));
|
|
418
|
+
this._raycasterPersonToCam = new THREE3.Raycaster(new THREE3.Vector3(), new THREE3.Vector3());
|
|
419
|
+
// ==================== 物理与碰撞检测 ====================
|
|
420
|
+
this.ensureAttributesMinimal = (geom) => {
|
|
421
|
+
if (!geom.attributes.position) return null;
|
|
422
|
+
if (!geom.attributes.normal) geom.computeVertexNormals();
|
|
423
|
+
if (!geom.attributes.uv) {
|
|
424
|
+
const count = geom.attributes.position.count;
|
|
425
|
+
const dummyUV = new Float32Array(count * 2);
|
|
426
|
+
geom.setAttribute("uv", new THREE3.BufferAttribute(dummyUV, 2));
|
|
427
|
+
}
|
|
428
|
+
return geom;
|
|
429
|
+
};
|
|
105
430
|
this.setAnimationByPressed = () => {
|
|
106
431
|
this._maxCamDistance = this.orginMaxCamDistance;
|
|
432
|
+
if (this.isMovingToBoardingPoint) {
|
|
433
|
+
this.isMovingToBoardingPoint = false;
|
|
434
|
+
this.boardingWaypoints = [];
|
|
435
|
+
this.currentWaypointIndex = 0;
|
|
436
|
+
this.boardingTargetDir = null;
|
|
437
|
+
}
|
|
438
|
+
if (this.enterVehicleTimer) {
|
|
439
|
+
clearTimeout(this.enterVehicleTimer);
|
|
440
|
+
this.enterVehicleTimer = null;
|
|
441
|
+
}
|
|
442
|
+
if (this.closeVehicleDoorTimer) {
|
|
443
|
+
clearTimeout(this.closeVehicleDoorTimer);
|
|
444
|
+
this.closeVehicleDoorTimer = null;
|
|
445
|
+
}
|
|
107
446
|
if (this.isFlying) {
|
|
108
447
|
if (!this.fwdPressed) {
|
|
109
448
|
this.playPersonAnimationByName("flyidle");
|
|
@@ -148,31 +487,33 @@ var PlayerController = class {
|
|
|
148
487
|
}, 200);
|
|
149
488
|
};
|
|
150
489
|
// ==================== 事件处理 ====================
|
|
151
|
-
/**
|
|
152
|
-
* 键盘按下事件
|
|
153
|
-
*/
|
|
154
490
|
this._boundOnKeydown = async (e) => {
|
|
155
491
|
if (e.ctrlKey && ["KeyW", "KeyA", "KeyS", "KeyD"].includes(e.code)) {
|
|
156
492
|
e.preventDefault();
|
|
157
493
|
}
|
|
158
494
|
switch (e.code) {
|
|
159
495
|
case "KeyW":
|
|
496
|
+
case "ArrowUp":
|
|
160
497
|
this.fwdPressed = true;
|
|
161
498
|
this.setAnimationByPressed();
|
|
162
499
|
break;
|
|
163
500
|
case "KeyS":
|
|
501
|
+
case "ArrowDown":
|
|
164
502
|
this.bkdPressed = true;
|
|
165
503
|
this.setAnimationByPressed();
|
|
166
504
|
break;
|
|
167
505
|
case "KeyD":
|
|
506
|
+
case "ArrowRight":
|
|
168
507
|
this.rgtPressed = true;
|
|
169
508
|
this.setAnimationByPressed();
|
|
170
509
|
break;
|
|
171
510
|
case "KeyA":
|
|
511
|
+
case "ArrowLeft":
|
|
172
512
|
this.lftPressed = true;
|
|
173
513
|
this.setAnimationByPressed();
|
|
174
514
|
break;
|
|
175
515
|
case "ShiftLeft":
|
|
516
|
+
case "ShiftRight":
|
|
176
517
|
this.shiftPressed = true;
|
|
177
518
|
this.setAnimationByPressed();
|
|
178
519
|
this.controls.mouseButtons = { LEFT: 2, MIDDLE: 1, RIGHT: 0 };
|
|
@@ -193,6 +534,7 @@ var PlayerController = class {
|
|
|
193
534
|
this.changeView();
|
|
194
535
|
break;
|
|
195
536
|
case "KeyF":
|
|
537
|
+
if (this.controllerMode == 1) return;
|
|
196
538
|
this.isFlying = !this.isFlying;
|
|
197
539
|
this.setAnimationByPressed();
|
|
198
540
|
if (!this.isFlying && !this.playerIsOnGround) {
|
|
@@ -200,32 +542,39 @@ var PlayerController = class {
|
|
|
200
542
|
}
|
|
201
543
|
break;
|
|
202
544
|
case "KeyE":
|
|
203
|
-
this.
|
|
545
|
+
if (this.isFlying) return;
|
|
546
|
+
if (this.controllerMode == 0) {
|
|
547
|
+
this.enterVehicle();
|
|
548
|
+
} else {
|
|
549
|
+
this.exitVehicle();
|
|
550
|
+
}
|
|
204
551
|
break;
|
|
205
552
|
}
|
|
206
553
|
};
|
|
207
|
-
/**
|
|
208
|
-
* 键盘抬起事件
|
|
209
|
-
*/
|
|
210
554
|
this._boundOnKeyup = (e) => {
|
|
211
555
|
switch (e.code) {
|
|
212
556
|
case "KeyW":
|
|
557
|
+
case "ArrowUp":
|
|
213
558
|
this.fwdPressed = false;
|
|
214
559
|
this.setAnimationByPressed();
|
|
215
560
|
break;
|
|
216
561
|
case "KeyS":
|
|
562
|
+
case "ArrowDown":
|
|
217
563
|
this.bkdPressed = false;
|
|
218
564
|
this.setAnimationByPressed();
|
|
219
565
|
break;
|
|
220
566
|
case "KeyD":
|
|
567
|
+
case "ArrowRight":
|
|
221
568
|
this.rgtPressed = false;
|
|
222
569
|
this.setAnimationByPressed();
|
|
223
570
|
break;
|
|
224
571
|
case "KeyA":
|
|
572
|
+
case "ArrowLeft":
|
|
225
573
|
this.lftPressed = false;
|
|
226
574
|
this.setAnimationByPressed();
|
|
227
575
|
break;
|
|
228
576
|
case "ShiftLeft":
|
|
577
|
+
case "ShiftRight":
|
|
229
578
|
this.shiftPressed = false;
|
|
230
579
|
this.setAnimationByPressed();
|
|
231
580
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
@@ -238,23 +587,14 @@ var PlayerController = class {
|
|
|
238
587
|
break;
|
|
239
588
|
}
|
|
240
589
|
};
|
|
241
|
-
/**
|
|
242
|
-
* 鼠标移动事件
|
|
243
|
-
*/
|
|
244
590
|
this._mouseMove = (e) => {
|
|
245
591
|
if (document.pointerLockElement !== document.body) return;
|
|
246
592
|
this.setToward(e.movementX, e.movementY, 1e-4);
|
|
247
593
|
};
|
|
248
|
-
|
|
249
|
-
* 鼠标点击事件
|
|
250
|
-
*/
|
|
251
|
-
this._mouseClick = (e) => {
|
|
594
|
+
this._mouseClick = (_e) => {
|
|
252
595
|
this.setPointerLock();
|
|
253
596
|
};
|
|
254
597
|
// ==================== 移动端控制 ====================
|
|
255
|
-
/**
|
|
256
|
-
* 指针按下事件
|
|
257
|
-
*/
|
|
258
598
|
this.onPointerDown = (e) => {
|
|
259
599
|
if (e.pointerType !== "touch") return;
|
|
260
600
|
this.isLookDown = true;
|
|
@@ -264,9 +604,6 @@ var PlayerController = class {
|
|
|
264
604
|
this.lookAreaEl?.setPointerCapture?.(e.pointerId);
|
|
265
605
|
e.preventDefault();
|
|
266
606
|
};
|
|
267
|
-
/**
|
|
268
|
-
* 指针移动事件
|
|
269
|
-
*/
|
|
270
607
|
this.onPointerMove = (e) => {
|
|
271
608
|
if (!this.isLookDown || e.pointerId !== this.lookPointerId) return;
|
|
272
609
|
const dx = e.clientX - this.lastTouchX;
|
|
@@ -276,9 +613,6 @@ var PlayerController = class {
|
|
|
276
613
|
this.setInput({ lookDeltaX: dx, lookDeltaY: dy });
|
|
277
614
|
e.preventDefault();
|
|
278
615
|
};
|
|
279
|
-
/**
|
|
280
|
-
* 指针抬起事件
|
|
281
|
-
*/
|
|
282
616
|
this.onPointerUp = (e) => {
|
|
283
617
|
if (e.pointerId !== this.lookPointerId) return;
|
|
284
618
|
this.isLookDown = false;
|
|
@@ -289,19 +623,15 @@ var PlayerController = class {
|
|
|
289
623
|
this._raycasterPersonToCam.firstHitOnly = true;
|
|
290
624
|
}
|
|
291
625
|
// ==================== 初始化相关方法 ====================
|
|
292
|
-
/**
|
|
293
|
-
* 初始化控制器
|
|
294
|
-
*/
|
|
295
626
|
async init(opts, callback) {
|
|
296
627
|
this.scene = opts.scene;
|
|
297
628
|
this.camera = opts.camera;
|
|
298
629
|
this.camera.rotation.order = "YXZ";
|
|
299
630
|
this.controls = opts.controls;
|
|
300
631
|
this.playerModel = opts.playerModel;
|
|
301
|
-
this.initPos = opts.initPos ?? new
|
|
632
|
+
this.initPos = opts.initPos ?? new THREE3.Vector3(0, 0, 0);
|
|
302
633
|
this.mouseSensity = opts.mouseSensity ?? 5;
|
|
303
634
|
const s = this.playerModel.scale;
|
|
304
|
-
this.visualizeDepth = 0 * s;
|
|
305
635
|
this.gravity = (opts.playerModel.gravity ?? -2400) * s;
|
|
306
636
|
this.jumpHeight = (opts.playerModel.jumpHeight ?? 800) * s;
|
|
307
637
|
this.originPlayerSpeed = (opts.playerModel.speed ?? 400) * s;
|
|
@@ -320,46 +650,97 @@ var PlayerController = class {
|
|
|
320
650
|
await this.initMobileControls();
|
|
321
651
|
}
|
|
322
652
|
await this.createBVH(opts.colliderMeshUrl);
|
|
323
|
-
this.createPlayer();
|
|
324
653
|
await this.loadPersonGLB();
|
|
325
|
-
if (this.isFirstPerson && this.person) {
|
|
326
|
-
this.person.add(this.camera);
|
|
327
|
-
}
|
|
328
654
|
this.onAllEvent();
|
|
329
655
|
this.setCameraPos();
|
|
330
656
|
this.setControls();
|
|
331
657
|
if (callback) callback();
|
|
332
658
|
}
|
|
333
|
-
/**
|
|
334
|
-
* 初始化加载器
|
|
335
|
-
*/
|
|
336
659
|
async initLoader() {
|
|
337
660
|
const dracoLoader = new DRACOLoader();
|
|
338
661
|
dracoLoader.setDecoderPath("https://unpkg.com/three@0.180.0/examples/jsm/libs/draco/gltf/");
|
|
339
662
|
dracoLoader.setDecoderConfig({ type: "js" });
|
|
340
663
|
this.loader.setDRACOLoader(dracoLoader);
|
|
341
664
|
}
|
|
665
|
+
async initRapier() {
|
|
666
|
+
if (this.RAPIER) return;
|
|
667
|
+
this.RAPIER = await import("./rapier.es-XQHNYU2P.mjs");
|
|
668
|
+
await this.RAPIER.init();
|
|
669
|
+
const gravity = new this.RAPIER.Vector3(0, -9.81, 0);
|
|
670
|
+
this.world = new this.RAPIER.World(gravity);
|
|
671
|
+
this.world.maxCcdSubsteps = 2;
|
|
672
|
+
const addGeometryAsTrimesh = (RAPIER, world, geom) => {
|
|
673
|
+
let geometry = geom.index ? geom.clone().toNonIndexed() : geom.clone();
|
|
674
|
+
const posAttr = geometry.attributes.position;
|
|
675
|
+
const vertexCount = posAttr.count;
|
|
676
|
+
if (vertexCount % 3 !== 0) {
|
|
677
|
+
console.warn("\u9876\u70B9\u6570\u4E0D\u662F3\u7684\u500D\u6570\uFF0C\u4E09\u89D2\u5F62\u53EF\u80FD\u4E0D\u5B8C\u6574");
|
|
678
|
+
}
|
|
679
|
+
const vertices = new Float32Array(vertexCount * 3);
|
|
680
|
+
const tmp = new THREE3.Vector3();
|
|
681
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
682
|
+
tmp.fromBufferAttribute(posAttr, i);
|
|
683
|
+
vertices[i * 3 + 0] = tmp.x;
|
|
684
|
+
vertices[i * 3 + 1] = tmp.y;
|
|
685
|
+
vertices[i * 3 + 2] = tmp.z;
|
|
686
|
+
}
|
|
687
|
+
const indices = vertexCount > 65535 ? new Uint32Array(vertexCount) : new Uint16Array(vertexCount);
|
|
688
|
+
for (let i = 0; i < vertexCount; i++) indices[i] = i;
|
|
689
|
+
const bodyDesc = RAPIER.RigidBodyDesc.fixed();
|
|
690
|
+
const body = world.createRigidBody(bodyDesc);
|
|
691
|
+
const colliderDesc = RAPIER.ColliderDesc.trimesh(vertices, indices).setRestitution(0).setFriction(0.8);
|
|
692
|
+
world.createCollider(colliderDesc, body);
|
|
693
|
+
};
|
|
694
|
+
for (const g of this.collected) {
|
|
695
|
+
addGeometryAsTrimesh(this.RAPIER, this.world, g);
|
|
696
|
+
}
|
|
697
|
+
const groundDesc = this.RAPIER.RigidBodyDesc.fixed();
|
|
698
|
+
const groundBody = this.world.createRigidBody(groundDesc);
|
|
699
|
+
groundBody.userData = { outOfBounds: true };
|
|
700
|
+
}
|
|
342
701
|
// ==================== 玩家模型相关方法 ====================
|
|
343
|
-
/**
|
|
344
|
-
* 加载玩家模型与动画
|
|
345
|
-
*/
|
|
346
702
|
async loadPersonGLB() {
|
|
347
703
|
try {
|
|
348
704
|
const gltf = await this.loader.loadAsync(this.playerModel.url);
|
|
349
705
|
this.person = gltf.scene;
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
|
|
706
|
+
const { size } = this.getBbox(this.person);
|
|
707
|
+
const ratio = this.playerHeight / size.y;
|
|
708
|
+
const power = Math.round(Math.log10(ratio));
|
|
709
|
+
const modelScale = Math.pow(10, power);
|
|
710
|
+
this.playerRadius = Number(Math.min(size.x, size.z).toFixed(0)) * modelScale;
|
|
711
|
+
this.playerHeight = Number(size.y.toFixed(0)) * modelScale;
|
|
712
|
+
const scale = this.playerModel.scale;
|
|
713
|
+
const material = new THREE3.MeshStandardMaterial({
|
|
714
|
+
color: new THREE3.Color(1, 0, 0),
|
|
715
|
+
shadowSide: THREE3.DoubleSide,
|
|
716
|
+
depthTest: false,
|
|
717
|
+
transparent: true,
|
|
718
|
+
opacity: this.displayPlayer ? 0.5 : 0,
|
|
719
|
+
wireframe: true,
|
|
720
|
+
depthWrite: false
|
|
721
|
+
});
|
|
722
|
+
const r = this.playerRadius * scale;
|
|
723
|
+
const h = this.playerHeight * scale;
|
|
724
|
+
this.player = new THREE3.Mesh(new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75), material);
|
|
725
|
+
this.player.geometry.translate(0, -h * 0.25, 0);
|
|
726
|
+
this.player.capsuleInfo = {
|
|
727
|
+
radius: r,
|
|
728
|
+
segment: new THREE3.Line3(new THREE3.Vector3(), new THREE3.Vector3(0, -h * 0.5, 0))
|
|
729
|
+
};
|
|
730
|
+
this.player.name = "capsule";
|
|
731
|
+
this.scene.add(this.player);
|
|
732
|
+
this.reset();
|
|
733
|
+
this.player.rotateY(this.playerModel.rotateY ?? 0);
|
|
734
|
+
this.person.scale.multiplyScalar(modelScale * scale);
|
|
353
735
|
this.person.position.set(0, -h * 0.75, 0);
|
|
354
736
|
this.person.traverse((child) => {
|
|
355
|
-
if (child.
|
|
356
|
-
|
|
357
|
-
child.receiveShadow = true;
|
|
737
|
+
if (child.name == this.playerModel?.headObjName) {
|
|
738
|
+
this.personHead = child;
|
|
358
739
|
}
|
|
359
740
|
});
|
|
360
741
|
this.player.add(this.person);
|
|
361
742
|
this.reset();
|
|
362
|
-
this.personMixer = new
|
|
743
|
+
this.personMixer = new THREE3.AnimationMixer(this.person);
|
|
363
744
|
const animations = gltf.animations ?? [];
|
|
364
745
|
this.personActions = /* @__PURE__ */ new Map();
|
|
365
746
|
const animationMappings = [
|
|
@@ -371,7 +752,9 @@ var PlayerController = class {
|
|
|
371
752
|
[this.playerModel.jumpAnim, "jumping"],
|
|
372
753
|
[this.playerModel.runAnim, "running"],
|
|
373
754
|
[this.playerModel.flyIdleAnim || this.playerModel.idleAnim, "flyidle"],
|
|
374
|
-
[this.playerModel.flyAnim || this.playerModel.idleAnim, "flying"]
|
|
755
|
+
[this.playerModel.flyAnim || this.playerModel.idleAnim, "flying"],
|
|
756
|
+
[this.playerModel.enterCarAnim || this.playerModel.idleAnim, "enterCar"],
|
|
757
|
+
[this.playerModel.exitCarAnim || this.playerModel.idleAnim, "exitCar"]
|
|
375
758
|
];
|
|
376
759
|
const findClip = (name) => animations.find((a) => a.name === name);
|
|
377
760
|
for (const [clipName, actionName] of animationMappings) {
|
|
@@ -379,11 +762,11 @@ var PlayerController = class {
|
|
|
379
762
|
if (!clip) continue;
|
|
380
763
|
const action = this.personMixer.clipAction(clip);
|
|
381
764
|
if (actionName === "jumping") {
|
|
382
|
-
action.setLoop(
|
|
765
|
+
action.setLoop(THREE3.LoopOnce, 1);
|
|
383
766
|
action.clampWhenFinished = true;
|
|
384
767
|
action.setEffectiveTimeScale(1.2);
|
|
385
768
|
} else {
|
|
386
|
-
action.setLoop(
|
|
769
|
+
action.setLoop(THREE3.LoopRepeat, Infinity);
|
|
387
770
|
action.clampWhenFinished = false;
|
|
388
771
|
action.setEffectiveTimeScale(1);
|
|
389
772
|
}
|
|
@@ -425,16 +808,20 @@ var PlayerController = class {
|
|
|
425
808
|
console.error("\u52A0\u8F7D\u73A9\u5BB6\u6A21\u578B\u5931\u8D25:", error);
|
|
426
809
|
}
|
|
427
810
|
}
|
|
428
|
-
/**
|
|
429
|
-
* 平滑切换人物动画
|
|
430
|
-
*/
|
|
431
811
|
playPersonAnimationByName(name, fade = 0.18) {
|
|
432
812
|
if (!this.personActions || this.ctPressed) return;
|
|
433
813
|
const next = this.personActions.get(name);
|
|
434
814
|
if (!next || this.actionState === next) return;
|
|
815
|
+
const duration = next.getClip().duration;
|
|
435
816
|
const prev = this.actionState;
|
|
436
817
|
next.reset();
|
|
437
818
|
next.setEffectiveWeight(1);
|
|
819
|
+
if (name == "enterCar" || name == "exitCar") {
|
|
820
|
+
const enterTime = this.activeVehicle?.enterVehicleTime ?? 1.5;
|
|
821
|
+
next.setEffectiveTimeScale(duration / enterTime);
|
|
822
|
+
next.setLoop(THREE3.LoopOnce, 1);
|
|
823
|
+
next.clampWhenFinished = true;
|
|
824
|
+
}
|
|
438
825
|
next.play();
|
|
439
826
|
if (prev && prev !== next) {
|
|
440
827
|
prev.fadeOut(fade);
|
|
@@ -444,106 +831,438 @@ var PlayerController = class {
|
|
|
444
831
|
}
|
|
445
832
|
this.actionState = next;
|
|
446
833
|
}
|
|
447
|
-
/**
|
|
448
|
-
* 创建玩家胶囊体
|
|
449
|
-
*/
|
|
450
|
-
createPlayer() {
|
|
451
|
-
const material = new THREE.MeshStandardMaterial({
|
|
452
|
-
color: new THREE.Color(1, 0, 0),
|
|
453
|
-
shadowSide: THREE.DoubleSide,
|
|
454
|
-
depthTest: false,
|
|
455
|
-
transparent: true,
|
|
456
|
-
opacity: this.displayPlayer ? 0.5 : 0,
|
|
457
|
-
wireframe: true,
|
|
458
|
-
depthWrite: false
|
|
459
|
-
});
|
|
460
|
-
const r = this.playerRadius * this.playerModel.scale;
|
|
461
|
-
const h = this.playerHeight * this.playerModel.scale;
|
|
462
|
-
this.player = new THREE.Mesh(new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75), material);
|
|
463
|
-
this.player.geometry.translate(0, -h * 0.25, 0);
|
|
464
|
-
this.player.capsuleInfo = {
|
|
465
|
-
radius: r,
|
|
466
|
-
segment: new THREE.Line3(new THREE.Vector3(), new THREE.Vector3(0, -h * 0.5, 0))
|
|
467
|
-
};
|
|
468
|
-
this.player.name = "capsule";
|
|
469
|
-
this.scene.add(this.player);
|
|
470
|
-
this.reset();
|
|
471
|
-
this.player.rotateY(this.playerModel.rotateY ?? 0);
|
|
472
|
-
}
|
|
473
834
|
// ==================== 车辆模型相关 ====================
|
|
474
835
|
/**
|
|
475
|
-
*
|
|
836
|
+
* 加载车辆模型
|
|
476
837
|
*/
|
|
477
|
-
async loadVehicleModel(
|
|
838
|
+
async loadVehicleModel(opts) {
|
|
478
839
|
try {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.
|
|
483
|
-
this.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
this.
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
[params.animations?.turnRightAnim ?? "", "turn_right"]
|
|
499
|
-
];
|
|
840
|
+
if (!this.playerModel.enterCarAnim) {
|
|
841
|
+
return console.warn("\u672A\u914D\u7F6E\u4E0A\u8F66\u52A8\u753B\uFF0C\u4E0D\u6267\u884C\u8F66\u8F86\u76F8\u5173\u903B\u8F91");
|
|
842
|
+
}
|
|
843
|
+
await this.initRapier();
|
|
844
|
+
if (!this.world) return;
|
|
845
|
+
const scale = opts.scale ?? 1;
|
|
846
|
+
const chassisRatio = opts.chassisRatio ?? 0.2;
|
|
847
|
+
const suspensionRestLengthRatio = opts.suspensionRestLengthRatio ?? 0.2;
|
|
848
|
+
this.vehicleParams.power.accelerateForce = 50 * scale;
|
|
849
|
+
this.vehicleParams.power.brakeForce = 200 * scale;
|
|
850
|
+
this.vehicleParams.power.maxSpeed = 1e4 * scale;
|
|
851
|
+
const vehicleModel = await this.loader.loadAsync(opts.url);
|
|
852
|
+
const { size: originalSize } = this.getBbox(vehicleModel.scene);
|
|
853
|
+
const ratio = this.vehicleLength / Math.max(originalSize.x, originalSize.y, originalSize.z);
|
|
854
|
+
const power = Math.round(Math.log10(ratio));
|
|
855
|
+
const modelScale = Math.pow(10, power);
|
|
856
|
+
const vehicleMixer = new THREE3.AnimationMixer(vehicleModel.scene);
|
|
857
|
+
const animations = vehicleModel.animations ?? [];
|
|
858
|
+
const vehicleActions = /* @__PURE__ */ new Map();
|
|
500
859
|
const findClip = (name) => animations.find((a) => a.name === name);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
action.setLoop(THREE.LoopOnce, 1);
|
|
860
|
+
const openDoorClip = findClip(opts.animations?.openDoorAnim || "");
|
|
861
|
+
if (openDoorClip) {
|
|
862
|
+
const action = vehicleMixer.clipAction(openDoorClip);
|
|
863
|
+
action.setLoop(THREE3.LoopOnce, 1);
|
|
506
864
|
action.clampWhenFinished = true;
|
|
507
|
-
action.setEffectiveTimeScale(
|
|
865
|
+
action.setEffectiveTimeScale(openDoorClip.duration);
|
|
508
866
|
action.enabled = true;
|
|
509
867
|
action.setEffectiveWeight(0);
|
|
510
|
-
|
|
868
|
+
vehicleActions.set("openDoor", action);
|
|
511
869
|
}
|
|
512
|
-
|
|
870
|
+
const wheelObjects = [];
|
|
871
|
+
for (const wheelName of opts.wheelsNames) {
|
|
872
|
+
let found = false;
|
|
873
|
+
vehicleModel.scene.traverse((child) => {
|
|
874
|
+
if (child.name === wheelName && !found) {
|
|
875
|
+
wheelObjects.push(child);
|
|
876
|
+
found = true;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
if (!found) console.warn(`\u672A\u627E\u5230\u8F6E\u5B50: ${wheelName}`);
|
|
880
|
+
}
|
|
881
|
+
const tempGroup = new THREE3.Group();
|
|
882
|
+
this.scene.add(tempGroup);
|
|
883
|
+
vehicleModel.scene.scale.multiplyScalar(modelScale * scale);
|
|
884
|
+
vehicleModel.scene.rotateY(this.vehicleParams.model.rotation);
|
|
885
|
+
const { size, bbox, center } = this.getBbox(vehicleModel.scene);
|
|
886
|
+
vehicleModel.scene.position.set(-center.x, -center.y, -center.z);
|
|
887
|
+
tempGroup.add(vehicleModel.scene);
|
|
888
|
+
tempGroup.updateMatrixWorld(true);
|
|
889
|
+
const wheelsInfo = [];
|
|
890
|
+
let wheelRadius = 0;
|
|
891
|
+
let wheelWidth = 0;
|
|
892
|
+
let suspensionRestLength = 0;
|
|
893
|
+
let chassisHeight = 0;
|
|
894
|
+
let wheelSizeInit = false;
|
|
895
|
+
for (let i = 0; i < wheelObjects.length; i++) {
|
|
896
|
+
const wheel = wheelObjects[i];
|
|
897
|
+
const worldPos = new THREE3.Vector3();
|
|
898
|
+
const worldQuat = new THREE3.Quaternion();
|
|
899
|
+
const worldScale = new THREE3.Vector3();
|
|
900
|
+
wheel.getWorldPosition(worldPos);
|
|
901
|
+
wheel.getWorldQuaternion(worldQuat);
|
|
902
|
+
wheel.getWorldScale(worldScale);
|
|
903
|
+
if (!wheelSizeInit) {
|
|
904
|
+
const { size: ws } = this.getBbox(wheel);
|
|
905
|
+
wheelRadius = Number((Math.max(ws.x, ws.y, ws.z) / 2).toFixed(2));
|
|
906
|
+
wheelWidth = Number(Math.min(ws.x, ws.y, ws.z).toFixed(2));
|
|
907
|
+
suspensionRestLength = Number((wheelRadius * 2 * suspensionRestLengthRatio).toFixed(2));
|
|
908
|
+
chassisHeight = Number((wheelRadius * 2 * chassisRatio).toFixed(2));
|
|
909
|
+
wheelSizeInit = true;
|
|
910
|
+
}
|
|
911
|
+
wheelsInfo.push({
|
|
912
|
+
axleCs: new THREE3.Vector3(0, 0, -1),
|
|
913
|
+
position: worldPos,
|
|
914
|
+
quaternion: worldQuat,
|
|
915
|
+
scale: worldScale,
|
|
916
|
+
radius: wheelRadius,
|
|
917
|
+
width: wheelWidth,
|
|
918
|
+
suspensionRestLength,
|
|
919
|
+
object: wheel
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
tempGroup.remove(vehicleModel.scene);
|
|
923
|
+
this.scene.remove(tempGroup);
|
|
924
|
+
const vehicleGroup = new THREE3.Group();
|
|
925
|
+
this.scene.add(vehicleGroup);
|
|
926
|
+
vehicleGroup.add(vehicleModel.scene);
|
|
927
|
+
vehicleGroup.updateMatrixWorld(true);
|
|
928
|
+
const wheelWrappers = [];
|
|
929
|
+
for (let i = 0; i < wheelsInfo.length; i++) {
|
|
930
|
+
const wheel = wheelsInfo[i];
|
|
931
|
+
const localPos = vehicleGroup.worldToLocal(wheel.position.clone());
|
|
932
|
+
const wheelWrapper = new THREE3.Group();
|
|
933
|
+
wheelWrapper.position.copy(localPos);
|
|
934
|
+
const wheelObj = wheelsInfo[i].object;
|
|
935
|
+
if (wheelObj.parent) wheelObj.parent.remove(wheelObj);
|
|
936
|
+
wheelObj.position.set(0, 0, 0);
|
|
937
|
+
wheelObj.quaternion.copy(wheel.quaternion);
|
|
938
|
+
wheelObj.scale.copy(wheel.scale);
|
|
939
|
+
wheelObj.updateMatrixWorld();
|
|
940
|
+
wheelWrapper.add(wheelObj);
|
|
941
|
+
vehicleGroup.add(wheelWrapper);
|
|
942
|
+
wheelWrappers.push(wheelWrapper);
|
|
943
|
+
}
|
|
944
|
+
const halfExtents = size.clone().multiplyScalar(0.5);
|
|
945
|
+
halfExtents.y -= chassisHeight / 2;
|
|
946
|
+
vehicleModel.scene.position.y -= chassisHeight / 2;
|
|
947
|
+
halfExtents.x *= 0.95;
|
|
948
|
+
halfExtents.z *= 0.95;
|
|
949
|
+
const chassisDesc = this.RAPIER.RigidBodyDesc.dynamic().setTranslation(opts.position.x, opts.position.y, opts.position.z).setLinearDamping(this.vehicleParams.chassis.linearDamping).setAngularDamping(this.vehicleParams.chassis.angularDamping).setCanSleep(false).setAdditionalMass(10);
|
|
950
|
+
const chassisBody = this.world.createRigidBody(chassisDesc);
|
|
951
|
+
const chassisCollider = this.RAPIER.ColliderDesc.cuboid(halfExtents.x, halfExtents.y, halfExtents.z);
|
|
952
|
+
this.world.createCollider(chassisCollider, chassisBody);
|
|
953
|
+
if (this.vehicleParams.debug.showPhysicsBox) {
|
|
954
|
+
const debugBox = new THREE3.Mesh(
|
|
955
|
+
new THREE3.BoxGeometry(halfExtents.x * 2, halfExtents.y * 2, halfExtents.z * 2),
|
|
956
|
+
new THREE3.MeshBasicMaterial({
|
|
957
|
+
color: 16711680,
|
|
958
|
+
wireframe: true,
|
|
959
|
+
transparent: true,
|
|
960
|
+
opacity: 0.3
|
|
961
|
+
})
|
|
962
|
+
);
|
|
963
|
+
vehicleGroup.add(debugBox);
|
|
964
|
+
}
|
|
965
|
+
vehicleGroup.position.copy(opts.position);
|
|
966
|
+
vehicleGroup.updateMatrixWorld(true);
|
|
967
|
+
const { vehicle, updateWheelVisuals } = createVehicleController(this.world, chassisBody, wheelWrappers, wheelsInfo);
|
|
968
|
+
const vehicleInstance = {
|
|
969
|
+
vehicleGroup,
|
|
970
|
+
chassisBody,
|
|
971
|
+
vehicleController: vehicle,
|
|
972
|
+
updateWheelVisuals,
|
|
973
|
+
vehicleMixer,
|
|
974
|
+
vehicleActions,
|
|
975
|
+
vehiclIsOpenDoor: false,
|
|
976
|
+
vehicleBBox: bbox.clone(),
|
|
977
|
+
pathPlanner: new PathPlanner(this._createObstacleCheckerFor(vehicleGroup, bbox, scale), {
|
|
978
|
+
debugEnabled: false,
|
|
979
|
+
scene: this.scene,
|
|
980
|
+
scale: this.playerModel.scale
|
|
981
|
+
}),
|
|
982
|
+
scale,
|
|
983
|
+
boardingPoint: opts.boardingPoint,
|
|
984
|
+
seatOffset: opts.seatOffset ?? new THREE3.Vector3(0, 0, 0),
|
|
985
|
+
enterVehicleTime: 1.5,
|
|
986
|
+
chassisRatio,
|
|
987
|
+
suspensionRestLengthRatio,
|
|
988
|
+
size: {
|
|
989
|
+
l: Math.max(size.x, size.z),
|
|
990
|
+
w: Math.min(size.x, size.z),
|
|
991
|
+
h: size.y
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
this.vehicles.push(vehicleInstance);
|
|
995
|
+
console.log(`\u8F66\u8F86\u52A0\u8F7D\u5B8C\u6210\uFF0C\u5F53\u524D\u5171 ${this.vehicles.length} \u8F86\u8F66`, vehicleInstance);
|
|
996
|
+
this.setControllerTransition();
|
|
513
997
|
} catch (error) {
|
|
514
998
|
console.error("\u52A0\u8F7D\u8F66\u8F86\u6A21\u578B\u5931\u8D25:", error);
|
|
515
999
|
}
|
|
516
1000
|
}
|
|
517
|
-
|
|
1001
|
+
getBbox(object) {
|
|
1002
|
+
const bbox = new THREE3.Box3().setFromObject(object);
|
|
1003
|
+
const center = new THREE3.Vector3();
|
|
1004
|
+
const size = new THREE3.Vector3();
|
|
1005
|
+
bbox.getCenter(center);
|
|
1006
|
+
bbox.getSize(size);
|
|
1007
|
+
return { bbox, center, size };
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* 为指定车辆创建障碍物检测器
|
|
1011
|
+
*/
|
|
1012
|
+
_createObstacleCheckerFor(vehicleGroup, bbox, scale) {
|
|
1013
|
+
return {
|
|
1014
|
+
isBlocked: (start, end) => {
|
|
1015
|
+
const vehiclePos = vehicleGroup.position;
|
|
1016
|
+
const vehicleQuat = vehicleGroup.quaternion;
|
|
1017
|
+
const center = new THREE3.Vector3();
|
|
1018
|
+
const size = new THREE3.Vector3();
|
|
1019
|
+
bbox.getCenter(center);
|
|
1020
|
+
bbox.getSize(size);
|
|
1021
|
+
center.applyQuaternion(vehicleQuat).add(vehiclePos);
|
|
1022
|
+
const halfSize = size.clone().multiplyScalar(0.5 * scale);
|
|
1023
|
+
const corners = [];
|
|
1024
|
+
for (let x = -1; x <= 1; x += 2) {
|
|
1025
|
+
for (let y = -1; y <= 1; y += 2) {
|
|
1026
|
+
for (let z = -1; z <= 1; z += 2) {
|
|
1027
|
+
const localCorner = new THREE3.Vector3(halfSize.x * x, halfSize.y * y, halfSize.z * z);
|
|
1028
|
+
const worldCorner = localCorner.applyQuaternion(vehicleQuat).add(center);
|
|
1029
|
+
corners.push(worldCorner);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const expandedBBox = new THREE3.Box3();
|
|
1034
|
+
corners.forEach((corner) => expandedBBox.expandByPoint(corner));
|
|
1035
|
+
expandedBBox.expandByScalar(100 * this.playerModel.scale);
|
|
1036
|
+
const direction = new THREE3.Vector3().subVectors(end, start);
|
|
1037
|
+
const length = direction.length();
|
|
1038
|
+
direction.normalize();
|
|
1039
|
+
const ray = new THREE3.Ray(start, direction);
|
|
1040
|
+
const intersection = new THREE3.Vector3();
|
|
1041
|
+
const intersects = ray.intersectBox(expandedBBox, intersection);
|
|
1042
|
+
return intersects !== null && start.distanceTo(intersection) < length;
|
|
1043
|
+
},
|
|
1044
|
+
getNavigationNodes: (start, _goal) => {
|
|
1045
|
+
const nodes = [];
|
|
1046
|
+
const vehiclePos = vehicleGroup.position;
|
|
1047
|
+
const vehicleQuat = vehicleGroup.quaternion;
|
|
1048
|
+
const vehicleForward = new THREE3.Vector3(0, 0, 1).applyQuaternion(vehicleQuat);
|
|
1049
|
+
const vehicleRight = new THREE3.Vector3(1, 0, 0).applyQuaternion(vehicleQuat);
|
|
1050
|
+
const bboxSize = new THREE3.Vector3();
|
|
1051
|
+
bbox.getSize(bboxSize);
|
|
1052
|
+
const halfLength = bboxSize.z / 2 * scale;
|
|
1053
|
+
const halfWidth = bboxSize.x / 2 * scale;
|
|
1054
|
+
const bypassMargin = 300 * this.playerModel.scale;
|
|
1055
|
+
const extendedMargin = 500 * this.playerModel.scale;
|
|
1056
|
+
const groundY = start.y;
|
|
1057
|
+
for (const margin of [bypassMargin, extendedMargin]) {
|
|
1058
|
+
nodes.push(
|
|
1059
|
+
vehiclePos.clone().add(vehicleForward.clone().multiplyScalar(halfLength + margin)).add(vehicleRight.clone().multiplyScalar(-halfWidth - margin)).setY(groundY)
|
|
1060
|
+
);
|
|
1061
|
+
nodes.push(
|
|
1062
|
+
vehiclePos.clone().add(vehicleForward.clone().multiplyScalar(halfLength + margin)).add(vehicleRight.clone().multiplyScalar(halfWidth + margin)).setY(groundY)
|
|
1063
|
+
);
|
|
1064
|
+
nodes.push(
|
|
1065
|
+
vehiclePos.clone().add(vehicleForward.clone().multiplyScalar(-halfLength - margin)).add(vehicleRight.clone().multiplyScalar(-halfWidth - margin)).setY(groundY)
|
|
1066
|
+
);
|
|
1067
|
+
nodes.push(
|
|
1068
|
+
vehiclePos.clone().add(vehicleForward.clone().multiplyScalar(-halfLength - margin)).add(vehicleRight.clone().multiplyScalar(halfWidth + margin)).setY(groundY)
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
return nodes;
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* 开关车门动画(操作当前 activeVehicle)
|
|
1077
|
+
*/
|
|
1078
|
+
openVehicleDoor(isOpen = true) {
|
|
1079
|
+
const v = this.activeVehicle;
|
|
1080
|
+
if (!v?.vehicleActions) return;
|
|
1081
|
+
const next = v.vehicleActions.get("openDoor");
|
|
1082
|
+
if (!next) return;
|
|
1083
|
+
const duration = next.getClip().duration;
|
|
1084
|
+
next.reset();
|
|
1085
|
+
next.setEffectiveWeight(1);
|
|
1086
|
+
if (isOpen) {
|
|
1087
|
+
next.setEffectiveTimeScale(duration * 2);
|
|
1088
|
+
next.time = 0;
|
|
1089
|
+
v.vehiclIsOpenDoor = true;
|
|
1090
|
+
} else {
|
|
1091
|
+
next.setEffectiveTimeScale(-duration * 2);
|
|
1092
|
+
next.time = duration;
|
|
1093
|
+
v.vehiclIsOpenDoor = false;
|
|
1094
|
+
}
|
|
1095
|
+
next.setLoop(THREE3.LoopOnce, 1);
|
|
1096
|
+
next.clampWhenFinished = true;
|
|
1097
|
+
next.play();
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* 上车:自动寻找最近的车辆
|
|
1101
|
+
*/
|
|
1102
|
+
enterVehicle() {
|
|
1103
|
+
if (this.vehicles.length === 0) return;
|
|
1104
|
+
let nearestVehicle = null;
|
|
1105
|
+
let nearestDist = Infinity;
|
|
1106
|
+
let nearBoardingPointWorld = null;
|
|
1107
|
+
for (const v2 of this.vehicles) {
|
|
1108
|
+
const boardingPointLocal = v2.boardingPoint.clone().multiplyScalar(v2.scale);
|
|
1109
|
+
const boardingPointWorld = new THREE3.Vector3();
|
|
1110
|
+
v2.vehicleGroup.localToWorld(boardingPointWorld.copy(boardingPointLocal));
|
|
1111
|
+
const dist = this.player.position.distanceTo(boardingPointWorld);
|
|
1112
|
+
if (dist < 800 * this.playerModel.scale && dist < nearestDist) {
|
|
1113
|
+
nearestDist = dist;
|
|
1114
|
+
nearestVehicle = v2;
|
|
1115
|
+
nearBoardingPointWorld = boardingPointWorld;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (!nearestVehicle || !nearBoardingPointWorld) return;
|
|
1119
|
+
this.activeVehicle = nearestVehicle;
|
|
1120
|
+
const v = nearestVehicle;
|
|
1121
|
+
this.boardingPointWorld = nearBoardingPointWorld;
|
|
1122
|
+
const vehicleForward = new THREE3.Vector3(0, 0, 1).applyQuaternion(v.vehicleGroup.quaternion).normalize();
|
|
1123
|
+
const path = v.pathPlanner.findPath(this.player.position.clone(), this.boardingPointWorld);
|
|
1124
|
+
this.boardingWaypoints = path;
|
|
1125
|
+
this.currentWaypointIndex = 0;
|
|
1126
|
+
this.boardingTargetDir = vehicleForward;
|
|
1127
|
+
this.isMovingToBoardingPoint = true;
|
|
1128
|
+
this.playPersonAnimationByName("walking");
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* 走向上车点
|
|
1132
|
+
*/
|
|
1133
|
+
updateMoveToBoardingPoint(delta) {
|
|
1134
|
+
if (!this.isMovingToBoardingPoint || !this.boardingTargetDir || this.boardingWaypoints.length === 0) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (this.currentWaypointIndex >= this.boardingWaypoints.length) {
|
|
1138
|
+
this.finalizeBoarding(delta);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const currentWaypoint = this.boardingWaypoints[this.currentWaypointIndex];
|
|
1142
|
+
const currentPos = this.player.position.clone();
|
|
1143
|
+
const horizontalDistance = new THREE3.Vector2(currentWaypoint.x - currentPos.x, currentWaypoint.z - currentPos.z).length();
|
|
1144
|
+
const isLastWaypoint = this.currentWaypointIndex === this.boardingWaypoints.length - 1;
|
|
1145
|
+
const waypointThreshold = isLastWaypoint ? 0 : 10 * this.playerModel.scale;
|
|
1146
|
+
if (horizontalDistance > waypointThreshold) {
|
|
1147
|
+
const moveDir = new THREE3.Vector3(currentWaypoint.x - currentPos.x, 0, currentWaypoint.z - currentPos.z).normalize();
|
|
1148
|
+
const moveDistance = Math.min(this.boardingMoveSpeed * this.playerModel.scale * delta, horizontalDistance);
|
|
1149
|
+
this.player.position.add(moveDir.multiplyScalar(moveDistance));
|
|
1150
|
+
const lookTarget = this.player.position.clone().add(moveDir);
|
|
1151
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
1152
|
+
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
1153
|
+
this.targetQuat.multiply(this.flip180Quat);
|
|
1154
|
+
const rotateAlpha = Math.min(1, this.boardingRotateSpeed * delta);
|
|
1155
|
+
this.player.quaternion.slerp(this.targetQuat, rotateAlpha);
|
|
1156
|
+
} else {
|
|
1157
|
+
this.currentWaypointIndex++;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
518
1160
|
/**
|
|
519
|
-
*
|
|
1161
|
+
* 完成上车
|
|
520
1162
|
*/
|
|
1163
|
+
finalizeBoarding(delta) {
|
|
1164
|
+
const v = this.activeVehicle;
|
|
1165
|
+
if (!this.boardingTargetDir || !v) return;
|
|
1166
|
+
const currentDir = new THREE3.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion).normalize();
|
|
1167
|
+
const targetDir = this.boardingTargetDir.clone().normalize();
|
|
1168
|
+
const angleDiff = currentDir.angleTo(targetDir);
|
|
1169
|
+
if (angleDiff > 0.01) {
|
|
1170
|
+
const lookTarget = this.player.position.clone().add(targetDir);
|
|
1171
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
1172
|
+
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
1173
|
+
const rotateAlpha = Math.min(1, this.boardingRotateSpeed * delta);
|
|
1174
|
+
this.player.quaternion.slerp(this.targetQuat, rotateAlpha);
|
|
1175
|
+
} else {
|
|
1176
|
+
this.boardingWaypoints = [];
|
|
1177
|
+
this.currentWaypointIndex = 0;
|
|
1178
|
+
this.boardingTargetDir = null;
|
|
1179
|
+
v.pathPlanner?.clearVisualization();
|
|
1180
|
+
this.playPersonAnimationByName("enterCar");
|
|
1181
|
+
if (!v.vehiclIsOpenDoor) this.openVehicleDoor();
|
|
1182
|
+
this.player.rotation.copy(v.vehicleGroup.rotation);
|
|
1183
|
+
this.player.quaternion.multiply(this.flip180Quat);
|
|
1184
|
+
this.closeVehicleDoorTimer = setTimeout(
|
|
1185
|
+
() => {
|
|
1186
|
+
this.openVehicleDoor(false);
|
|
1187
|
+
},
|
|
1188
|
+
v.enterVehicleTime * 1e3 - 500
|
|
1189
|
+
);
|
|
1190
|
+
this.enterVehicleTimer = setTimeout(() => {
|
|
1191
|
+
if (this.boardingPointWorld) {
|
|
1192
|
+
const offsetY = this.boardingPointWorld.y - this.player.position.y;
|
|
1193
|
+
this.controllerMode = 1;
|
|
1194
|
+
v.vehicleGroup.attach(this.player);
|
|
1195
|
+
this.player.position.add(
|
|
1196
|
+
v.seatOffset.clone().multiplyScalar(v.scale).add(new THREE3.Vector3(0, offsetY, 0))
|
|
1197
|
+
);
|
|
1198
|
+
this.isMovingToBoardingPoint = false;
|
|
1199
|
+
if (this.isChangeControllerTransitionTimer) {
|
|
1200
|
+
clearTimeout(this.isChangeControllerTransitionTimer);
|
|
1201
|
+
this.isChangeControllerTransitionTimer = null;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}, v.enterVehicleTime * 1e3);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* 下车
|
|
1209
|
+
*/
|
|
1210
|
+
exitVehicle() {
|
|
1211
|
+
const v = this.activeVehicle;
|
|
1212
|
+
if (!v) return;
|
|
1213
|
+
this.isMovingToBoardingPoint = false;
|
|
1214
|
+
this.boardingWaypoints = [];
|
|
1215
|
+
this.currentWaypointIndex = 0;
|
|
1216
|
+
this.boardingTargetDir = null;
|
|
1217
|
+
const vel = v.chassisBody.linvel();
|
|
1218
|
+
const velSpeed = new THREE3.Vector3(vel.x, vel.y, vel.z).length();
|
|
1219
|
+
if (velSpeed > 0.1) {
|
|
1220
|
+
this.playPersonAnimationByName("idle");
|
|
1221
|
+
} else {
|
|
1222
|
+
this.playPersonAnimationByName("exitCar");
|
|
1223
|
+
}
|
|
1224
|
+
this.openVehicleDoor(true);
|
|
1225
|
+
this.closeVehicleDoorTimer = setTimeout(
|
|
1226
|
+
() => {
|
|
1227
|
+
this.openVehicleDoor(false);
|
|
1228
|
+
},
|
|
1229
|
+
v.enterVehicleTime * 1e3 - 500
|
|
1230
|
+
);
|
|
1231
|
+
this.controllerMode = 0;
|
|
1232
|
+
this.scene.attach(this.player);
|
|
1233
|
+
if (this.isFirstPerson) {
|
|
1234
|
+
this.setFirstPersonCamera();
|
|
1235
|
+
}
|
|
1236
|
+
this.setControllerTransition();
|
|
1237
|
+
}
|
|
1238
|
+
// ==================== 相机与视角控制 ====================
|
|
521
1239
|
changeView() {
|
|
522
1240
|
this.isFirstPerson = !this.isFirstPerson;
|
|
523
1241
|
if (this.isFirstPerson) {
|
|
524
|
-
this.
|
|
525
|
-
this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
|
|
526
|
-
this.camera.rotation.set(0, Math.PI, 0);
|
|
527
|
-
this.controls.enableZoom = false;
|
|
1242
|
+
this.setFirstPersonCamera();
|
|
528
1243
|
} else {
|
|
529
1244
|
this.scene.attach(this.camera);
|
|
530
1245
|
const worldPos = this.player.position.clone();
|
|
531
|
-
const dir = new
|
|
1246
|
+
const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
|
|
532
1247
|
const angle = Math.atan2(dir.z, dir.x);
|
|
533
|
-
const offset = new
|
|
1248
|
+
const offset = new THREE3.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, 200 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
|
|
534
1249
|
this.camera.position.copy(worldPos).add(offset);
|
|
535
1250
|
this.controls.target.copy(worldPos);
|
|
536
1251
|
this.controls.enableZoom = this.enableZoom;
|
|
537
1252
|
}
|
|
538
1253
|
this.setPointerLock();
|
|
539
1254
|
}
|
|
540
|
-
|
|
541
|
-
this.
|
|
542
|
-
|
|
1255
|
+
setFirstPersonCamera() {
|
|
1256
|
+
if (this.personHead) {
|
|
1257
|
+
this.personHead?.attach(this.camera);
|
|
1258
|
+
this.camera.position.set(0, 10, 20);
|
|
1259
|
+
} else {
|
|
1260
|
+
this.player.attach(this.camera);
|
|
1261
|
+
this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
|
|
1262
|
+
}
|
|
1263
|
+
this.camera.rotation.set(0, Math.PI, 0);
|
|
1264
|
+
this.controls.enableZoom = false;
|
|
543
1265
|
}
|
|
544
|
-
/**
|
|
545
|
-
* 设置指针锁定
|
|
546
|
-
*/
|
|
547
1266
|
setPointerLock() {
|
|
548
1267
|
if ((this.thirdMouseMode === 0 || this.thirdMouseMode === 1) && !this.isFirstPerson || this.isFirstPerson) {
|
|
549
1268
|
document.body.requestPointerLock();
|
|
@@ -551,33 +1270,28 @@ var PlayerController = class {
|
|
|
551
1270
|
document.exitPointerLock();
|
|
552
1271
|
}
|
|
553
1272
|
}
|
|
554
|
-
/**
|
|
555
|
-
* 设置摄像机初始位置
|
|
556
|
-
*/
|
|
557
1273
|
setCameraPos() {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
1274
|
+
setTimeout(() => {
|
|
1275
|
+
if (this.isFirstPerson) {
|
|
1276
|
+
this.person.add(this.camera);
|
|
1277
|
+
this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
|
|
1278
|
+
} else {
|
|
1279
|
+
const worldPos = this.player.position.clone();
|
|
1280
|
+
const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
|
|
1281
|
+
const angle = Math.atan2(dir.z, dir.x);
|
|
1282
|
+
const offset = new THREE3.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, -100 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
|
|
1283
|
+
this.camera.position.copy(worldPos).add(offset);
|
|
1284
|
+
this.controls.enableZoom = this.enableZoom;
|
|
1285
|
+
}
|
|
1286
|
+
this.camera.updateProjectionMatrix();
|
|
1287
|
+
}, 0);
|
|
568
1288
|
}
|
|
569
|
-
/**
|
|
570
|
-
* 设置控制器
|
|
571
|
-
*/
|
|
572
1289
|
setControls() {
|
|
573
1290
|
this.controls.enableZoom = this.enableZoom;
|
|
574
1291
|
this.controls.rotateSpeed = this.mouseSensity * 0.05;
|
|
575
1292
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
576
1293
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
577
1294
|
}
|
|
578
|
-
/**
|
|
579
|
-
* 重置控制器
|
|
580
|
-
*/
|
|
581
1295
|
resetControls() {
|
|
582
1296
|
if (!this.controls) return;
|
|
583
1297
|
this.controls.enabled = true;
|
|
@@ -587,38 +1301,60 @@ var PlayerController = class {
|
|
|
587
1301
|
this.controls.enableZoom = true;
|
|
588
1302
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
589
1303
|
}
|
|
590
|
-
/**
|
|
591
|
-
* 设置朝向
|
|
592
|
-
*/
|
|
593
1304
|
setToward(dx, dy, speed) {
|
|
594
|
-
if (this.
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1305
|
+
if (this.controllerMode == 0) {
|
|
1306
|
+
if (this.isFirstPerson) {
|
|
1307
|
+
if (this.isMovingToBoardingPoint) return;
|
|
1308
|
+
const yaw = -dx * speed * this.mouseSensity;
|
|
1309
|
+
const pitch = -dy * speed * this.mouseSensity;
|
|
1310
|
+
this.player.rotateY(yaw);
|
|
1311
|
+
this.camera.rotation.x = THREE3.MathUtils.clamp(this.camera.rotation.x + pitch, -1.1, 1.4);
|
|
1312
|
+
} else {
|
|
1313
|
+
const sensitivity = this.mouseSensity;
|
|
1314
|
+
const deltaX = -dx * speed * sensitivity;
|
|
1315
|
+
const deltaY = -dy * speed * sensitivity;
|
|
1316
|
+
const target = this.player.position.clone();
|
|
1317
|
+
const distance = this.camera.position.distanceTo(target);
|
|
1318
|
+
const currentPosition = this.camera.position.clone().sub(target);
|
|
1319
|
+
let theta = Math.atan2(currentPosition.x, currentPosition.z);
|
|
1320
|
+
let phi = Math.acos(currentPosition.y / distance);
|
|
1321
|
+
theta += deltaX;
|
|
1322
|
+
phi += deltaY;
|
|
1323
|
+
phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
|
|
1324
|
+
const newX = distance * Math.sin(phi) * Math.sin(theta);
|
|
1325
|
+
const newY = distance * Math.cos(phi);
|
|
1326
|
+
const newZ = distance * Math.sin(phi) * Math.cos(theta);
|
|
1327
|
+
this.camera.position.set(target.x + newX, target.y + newY, target.z + newZ);
|
|
1328
|
+
this.camera.lookAt(target);
|
|
1329
|
+
}
|
|
599
1330
|
} else {
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1331
|
+
const v = this.activeVehicle;
|
|
1332
|
+
if (!v) return;
|
|
1333
|
+
if (this.isFirstPerson) {
|
|
1334
|
+
const yaw = -dx * speed * this.mouseSensity;
|
|
1335
|
+
const pitch = -dy * speed * this.mouseSensity;
|
|
1336
|
+
this.camera.rotation.y = THREE3.MathUtils.clamp(this.camera.rotation.y + yaw, Math.PI * (3 / 4), Math.PI * (5 / 4));
|
|
1337
|
+
this.camera.rotation.x = THREE3.MathUtils.clamp(this.camera.rotation.x + pitch, 0, Math.PI * (1 / 3));
|
|
1338
|
+
} else {
|
|
1339
|
+
const sensitivity = this.mouseSensity;
|
|
1340
|
+
const deltaX = -dx * speed * sensitivity;
|
|
1341
|
+
const deltaY = -dy * speed * sensitivity;
|
|
1342
|
+
const target = v.vehicleGroup.position.clone();
|
|
1343
|
+
const distance = this.camera.position.distanceTo(target);
|
|
1344
|
+
const currentPosition = this.camera.position.clone().sub(target);
|
|
1345
|
+
let theta = Math.atan2(currentPosition.x, currentPosition.z);
|
|
1346
|
+
let phi = Math.acos(currentPosition.y / distance);
|
|
1347
|
+
theta += deltaX;
|
|
1348
|
+
phi += deltaY;
|
|
1349
|
+
phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
|
|
1350
|
+
const newX = distance * Math.sin(phi) * Math.sin(theta);
|
|
1351
|
+
const newY = distance * Math.cos(phi);
|
|
1352
|
+
const newZ = distance * Math.sin(phi) * Math.cos(theta);
|
|
1353
|
+
this.camera.position.set(target.x + newX, target.y + newY, target.z + newZ);
|
|
1354
|
+
this.camera.lookAt(target);
|
|
1355
|
+
}
|
|
616
1356
|
}
|
|
617
1357
|
}
|
|
618
|
-
// ==================== 物理与碰撞检测 ====================
|
|
619
|
-
/**
|
|
620
|
-
* 统一属性集合
|
|
621
|
-
*/
|
|
622
1358
|
unifiedAttribute(collected) {
|
|
623
1359
|
const attrMap = /* @__PURE__ */ new Map();
|
|
624
1360
|
const attrConflict = /* @__PURE__ */ new Set();
|
|
@@ -665,28 +1401,14 @@ var PlayerController = class {
|
|
|
665
1401
|
const meta = attrMap.get(name);
|
|
666
1402
|
const len = count * meta.itemSize;
|
|
667
1403
|
const array = new meta.arrayCtor(len);
|
|
668
|
-
g.setAttribute(name, new
|
|
1404
|
+
g.setAttribute(name, new THREE3.BufferAttribute(array, meta.itemSize, meta.normalized));
|
|
669
1405
|
}
|
|
670
1406
|
}
|
|
671
1407
|
}
|
|
672
1408
|
return collected;
|
|
673
1409
|
}
|
|
674
|
-
/**
|
|
675
|
-
* BVH碰撞体构建
|
|
676
|
-
*/
|
|
677
1410
|
async createBVH(meshUrl = "") {
|
|
678
1411
|
await this.initLoader();
|
|
679
|
-
const ensureAttributesMinimal = (geom) => {
|
|
680
|
-
if (!geom.attributes.position) return null;
|
|
681
|
-
if (!geom.attributes.normal) geom.computeVertexNormals();
|
|
682
|
-
if (!geom.attributes.uv) {
|
|
683
|
-
const count = geom.attributes.position.count;
|
|
684
|
-
const dummyUV = new Float32Array(count * 2);
|
|
685
|
-
geom.setAttribute("uv", new THREE.BufferAttribute(dummyUV, 2));
|
|
686
|
-
}
|
|
687
|
-
return geom;
|
|
688
|
-
};
|
|
689
|
-
let collected = [];
|
|
690
1412
|
if (meshUrl === "") {
|
|
691
1413
|
if (this.collider) {
|
|
692
1414
|
this.scene.remove(this.collider);
|
|
@@ -699,15 +1421,15 @@ var PlayerController = class {
|
|
|
699
1421
|
let geom = mesh.geometry.clone();
|
|
700
1422
|
geom.applyMatrix4(mesh.matrixWorld);
|
|
701
1423
|
if (geom.index) geom = geom.toNonIndexed();
|
|
702
|
-
const safe = ensureAttributesMinimal(geom);
|
|
703
|
-
if (safe) collected.push(safe);
|
|
1424
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1425
|
+
if (safe) this.collected.push(safe);
|
|
704
1426
|
} catch (e) {
|
|
705
1427
|
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
706
1428
|
}
|
|
707
1429
|
}
|
|
708
1430
|
});
|
|
709
|
-
if (!collected.length) return;
|
|
710
|
-
collected = this.unifiedAttribute(collected);
|
|
1431
|
+
if (!this.collected.length) return;
|
|
1432
|
+
this.collected = this.unifiedAttribute(this.collected);
|
|
711
1433
|
} else {
|
|
712
1434
|
const gltf = await this.loader.loadAsync(meshUrl);
|
|
713
1435
|
const mesh = gltf.scene.children[0];
|
|
@@ -715,34 +1437,73 @@ var PlayerController = class {
|
|
|
715
1437
|
let geom = mesh.geometry.clone();
|
|
716
1438
|
geom.applyMatrix4(mesh.matrixWorld);
|
|
717
1439
|
if (geom.index) geom = geom.toNonIndexed();
|
|
718
|
-
const safe = ensureAttributesMinimal(geom);
|
|
719
|
-
if (safe) collected.push(safe);
|
|
1440
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1441
|
+
if (safe) this.collected.push(safe);
|
|
720
1442
|
}
|
|
721
|
-
const merged = BufferGeometryUtils.mergeGeometries(collected, false);
|
|
1443
|
+
const merged = BufferGeometryUtils.mergeGeometries(this.collected, false);
|
|
722
1444
|
if (!merged) {
|
|
723
1445
|
console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
|
|
724
1446
|
return;
|
|
725
1447
|
}
|
|
726
1448
|
merged.boundsTree = new MeshBVH(merged, { maxDepth: 100 });
|
|
727
|
-
this.collider = new
|
|
1449
|
+
this.collider = new THREE3.Mesh(
|
|
728
1450
|
merged,
|
|
729
|
-
new
|
|
1451
|
+
new THREE3.MeshBasicMaterial({
|
|
730
1452
|
opacity: 0.5,
|
|
731
1453
|
transparent: true,
|
|
732
|
-
wireframe: true
|
|
1454
|
+
wireframe: true,
|
|
1455
|
+
depthTest: true
|
|
733
1456
|
})
|
|
734
1457
|
);
|
|
735
1458
|
if (this.displayCollider) this.scene.add(this.collider);
|
|
736
1459
|
if (this.displayVisualizer) {
|
|
737
1460
|
if (this.visualizer) this.scene.remove(this.visualizer);
|
|
738
|
-
this.visualizer = new MeshBVHHelper(this.collider,
|
|
1461
|
+
this.visualizer = new MeshBVHHelper(this.collider, 0);
|
|
739
1462
|
this.scene.add(this.visualizer);
|
|
740
1463
|
}
|
|
741
1464
|
this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
|
|
742
1465
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1466
|
+
createDynamicBVH(objects = []) {
|
|
1467
|
+
if (this.dynamicCollider) {
|
|
1468
|
+
this.scene.remove(this.dynamicCollider);
|
|
1469
|
+
this.dynamicCollider = null;
|
|
1470
|
+
}
|
|
1471
|
+
this.dynamicCollected = [];
|
|
1472
|
+
objects.forEach((object) => {
|
|
1473
|
+
object.traverse((c) => {
|
|
1474
|
+
const mesh = c;
|
|
1475
|
+
if (mesh?.isMesh && mesh.geometry && c.name !== "capsule") {
|
|
1476
|
+
try {
|
|
1477
|
+
let geom = mesh.geometry.clone();
|
|
1478
|
+
geom.applyMatrix4(mesh.matrixWorld);
|
|
1479
|
+
if (geom.index) geom = geom.toNonIndexed();
|
|
1480
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1481
|
+
if (safe) this.dynamicCollected.push(safe);
|
|
1482
|
+
} catch (e) {
|
|
1483
|
+
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
if (!this.dynamicCollected.length) return;
|
|
1489
|
+
this.dynamicCollected = this.unifiedAttribute(this.dynamicCollected);
|
|
1490
|
+
const merged = BufferGeometryUtils.mergeGeometries(this.dynamicCollected, false);
|
|
1491
|
+
if (!merged) {
|
|
1492
|
+
console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
merged.boundsTree = new MeshBVH(merged);
|
|
1496
|
+
this.dynamicCollider = new THREE3.Mesh(
|
|
1497
|
+
merged,
|
|
1498
|
+
new THREE3.MeshBasicMaterial({
|
|
1499
|
+
opacity: 0.5,
|
|
1500
|
+
transparent: true,
|
|
1501
|
+
wireframe: true,
|
|
1502
|
+
depthTest: true
|
|
1503
|
+
})
|
|
1504
|
+
);
|
|
1505
|
+
if (this.displayCollider) this.scene.add(this.dynamicCollider);
|
|
1506
|
+
}
|
|
746
1507
|
getAngleWithYAxis(normal) {
|
|
747
1508
|
const yAxis = { x: 0, y: 1, z: 0 };
|
|
748
1509
|
const dotProduct = normal.x * yAxis.x + normal.y * yAxis.y + normal.z * yAxis.z;
|
|
@@ -750,13 +1511,193 @@ var PlayerController = class {
|
|
|
750
1511
|
const cosTheta = dotProduct / normalMagnitude;
|
|
751
1512
|
return Math.acos(cosTheta);
|
|
752
1513
|
}
|
|
1514
|
+
// ==================== 设置控制器过渡 ====================
|
|
1515
|
+
setControllerTransition() {
|
|
1516
|
+
if (this.isChangeControllerTransitionTimer) {
|
|
1517
|
+
clearTimeout(this.isChangeControllerTransitionTimer);
|
|
1518
|
+
this.isChangeControllerTransitionTimer = null;
|
|
1519
|
+
}
|
|
1520
|
+
this.isChangeControllerTransitionTimer = setTimeout(() => {
|
|
1521
|
+
this.isChangeControllerTransitionTimer = null;
|
|
1522
|
+
let vGroups = [];
|
|
1523
|
+
for (const v of this.vehicles) {
|
|
1524
|
+
this.clearVehicleVelocity(v);
|
|
1525
|
+
vGroups.push(v.vehicleGroup);
|
|
1526
|
+
}
|
|
1527
|
+
this.createDynamicBVH(vGroups);
|
|
1528
|
+
}, 3e3);
|
|
1529
|
+
}
|
|
1530
|
+
// 清除车辆速度
|
|
1531
|
+
clearVehicleVelocity(v) {
|
|
1532
|
+
if (!v || !this.world || !this.RAPIER) return;
|
|
1533
|
+
const { chassisBody, vehicleController } = v;
|
|
1534
|
+
const ZERO = new this.RAPIER.Vector3(0, 0, 0);
|
|
1535
|
+
chassisBody.setLinvel(ZERO, true);
|
|
1536
|
+
chassisBody.setAngvel(ZERO, true);
|
|
1537
|
+
const BIG_BRAKE = 1e6;
|
|
1538
|
+
for (let i = 0; i < 4; i++) {
|
|
1539
|
+
vehicleController.setWheelEngineForce(i, 0);
|
|
1540
|
+
vehicleController.setWheelBrake(i, BIG_BRAKE);
|
|
1541
|
+
}
|
|
1542
|
+
vehicleController.updateVehicle(1 / 60);
|
|
1543
|
+
this.world.timestep = 1 / 60;
|
|
1544
|
+
this.world.step();
|
|
1545
|
+
chassisBody.setLinvel(ZERO, true);
|
|
1546
|
+
chassisBody.setAngvel(ZERO, true);
|
|
1547
|
+
for (let i = 0; i < 4; i++) {
|
|
1548
|
+
vehicleController.setWheelBrake(i, 0);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
753
1551
|
// ==================== 循环更新 ====================
|
|
754
|
-
/**
|
|
755
|
-
* 每帧更新
|
|
756
|
-
*/
|
|
757
1552
|
async update(delta = clock.getDelta()) {
|
|
758
1553
|
if (!this.isupdate || !this.player || !this.collider) return;
|
|
759
1554
|
delta = Math.min(delta, 1 / 30);
|
|
1555
|
+
if (this.controllerMode == 1) {
|
|
1556
|
+
this.updateVehicle(delta);
|
|
1557
|
+
} else {
|
|
1558
|
+
this.updatePlayer(delta);
|
|
1559
|
+
if (this.isChangeControllerTransitionTimer) this.updateVehicleInertia(delta);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* 更新当前驾驶的车辆
|
|
1564
|
+
*/
|
|
1565
|
+
updateVehicle(delta) {
|
|
1566
|
+
const v = this.activeVehicle;
|
|
1567
|
+
if (!v || !this.world) return;
|
|
1568
|
+
const { vehicleController, chassisBody, vehicleGroup } = v;
|
|
1569
|
+
const rotation = chassisBody.rotation();
|
|
1570
|
+
const quat = new THREE3.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w);
|
|
1571
|
+
const forward = new THREE3.Vector3(1, 0, 0).applyQuaternion(quat);
|
|
1572
|
+
const slopeAngle = Math.asin(forward.y);
|
|
1573
|
+
let factor = 1;
|
|
1574
|
+
if (slopeAngle < -0.05 && this.fwdPressed) {
|
|
1575
|
+
factor = -Math.sin(slopeAngle) * 10;
|
|
1576
|
+
}
|
|
1577
|
+
const engineForce = (Number(this.fwdPressed) * this.vehicleParams.power.accelerateForce - Number(this.bkdPressed) * this.vehicleParams.power.accelerateForce) * factor;
|
|
1578
|
+
vehicleController.setWheelEngineForce(0, engineForce);
|
|
1579
|
+
vehicleController.setWheelEngineForce(1, engineForce);
|
|
1580
|
+
vehicleController.setWheelEngineForce(2, engineForce);
|
|
1581
|
+
vehicleController.setWheelEngineForce(3, engineForce);
|
|
1582
|
+
const wheelBrake = Number(this.spacePressed) * this.vehicleParams.power.brakeForce * delta;
|
|
1583
|
+
vehicleController.setWheelBrake(0, wheelBrake);
|
|
1584
|
+
vehicleController.setWheelBrake(1, wheelBrake);
|
|
1585
|
+
vehicleController.setWheelBrake(2, wheelBrake);
|
|
1586
|
+
vehicleController.setWheelBrake(3, wheelBrake);
|
|
1587
|
+
const currentSteering = vehicleController.wheelSteering(0) || 0;
|
|
1588
|
+
const steerDirection = Number(this.lftPressed) - Number(this.rgtPressed);
|
|
1589
|
+
let steerSpeed;
|
|
1590
|
+
if (steerDirection === 0) {
|
|
1591
|
+
steerSpeed = this.vehicleParams.steering.steerReturnSpeed || 0.15;
|
|
1592
|
+
} else {
|
|
1593
|
+
steerSpeed = this.vehicleParams.steering.steerSpeed || 0.08;
|
|
1594
|
+
}
|
|
1595
|
+
const steerLerpFactor = 1 - Math.pow(1 - steerSpeed, delta);
|
|
1596
|
+
const targetSteering = this.vehicleParams.steering.maxSteerAngle * steerDirection;
|
|
1597
|
+
const steering = THREE3.MathUtils.lerp(currentSteering, targetSteering, steerLerpFactor);
|
|
1598
|
+
vehicleController.setWheelSteering(0, steering);
|
|
1599
|
+
vehicleController.setWheelSteering(1, steering);
|
|
1600
|
+
if ((this.rgtPressed || this.lftPressed) && this.shiftPressed) {
|
|
1601
|
+
vehicleController.setWheelSideFrictionStiffness(2, 0.5);
|
|
1602
|
+
vehicleController.setWheelSideFrictionStiffness(3, 0.5);
|
|
1603
|
+
} else {
|
|
1604
|
+
vehicleController.setWheelSideFrictionStiffness(2, 2);
|
|
1605
|
+
vehicleController.setWheelSideFrictionStiffness(3, 2);
|
|
1606
|
+
}
|
|
1607
|
+
this.updateVehicleInertia(delta);
|
|
1608
|
+
if (!this.isFirstPerson) {
|
|
1609
|
+
const lookTarget = vehicleGroup.position.clone();
|
|
1610
|
+
this.camera.position.sub(this.controls.target);
|
|
1611
|
+
this.controls.target.copy(lookTarget);
|
|
1612
|
+
this.camera.position.add(lookTarget);
|
|
1613
|
+
this.controls.update();
|
|
1614
|
+
const velocity = chassisBody.linvel();
|
|
1615
|
+
const currentSpeed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
|
|
1616
|
+
const maxSpeed = this.vehicleParams.power.maxSpeed * v.scale;
|
|
1617
|
+
const speedRatio = Math.min(currentSpeed / maxSpeed, 1);
|
|
1618
|
+
const baseCamDistance = v.size.l * 0.8;
|
|
1619
|
+
const maxCamDistanceLimit = v.size.l * 5;
|
|
1620
|
+
const targetDistance = THREE3.MathUtils.lerp(baseCamDistance, maxCamDistanceLimit, speedRatio);
|
|
1621
|
+
this._personToCam.subVectors(this.camera.position, vehicleGroup.position);
|
|
1622
|
+
const origin = vehicleGroup.position.clone().add(new THREE3.Vector3(0, 0, 0));
|
|
1623
|
+
const direction = this._personToCam.clone().normalize();
|
|
1624
|
+
const desiredDist = targetDistance;
|
|
1625
|
+
this._raycasterPersonToCam.set(origin, direction);
|
|
1626
|
+
this._raycasterPersonToCam.far = desiredDist;
|
|
1627
|
+
const intersects = this._raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1628
|
+
if (intersects.length > 0) {
|
|
1629
|
+
const hit = intersects[0];
|
|
1630
|
+
const safeDist = Math.max(hit.distance - this._camEpsilon, this._minCamDistance);
|
|
1631
|
+
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
1632
|
+
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
1633
|
+
} else {
|
|
1634
|
+
this._raycasterPersonToCam.far = maxCamDistanceLimit;
|
|
1635
|
+
const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1636
|
+
let safeDist = desiredDist;
|
|
1637
|
+
if (intersectsMaxDis.length) {
|
|
1638
|
+
const hitMax = intersectsMaxDis[0];
|
|
1639
|
+
safeDist = Math.min(desiredDist, hitMax.distance - this._camEpsilon);
|
|
1640
|
+
}
|
|
1641
|
+
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
1642
|
+
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
1643
|
+
}
|
|
1644
|
+
if (this.fwdPressed || this.bkdPressed) {
|
|
1645
|
+
const vel = chassisBody.linvel();
|
|
1646
|
+
const velHorizontal = new THREE3.Vector3(vel.x, vel.y, vel.z);
|
|
1647
|
+
const velSpeed = velHorizontal.length();
|
|
1648
|
+
if (velSpeed > 0.3) {
|
|
1649
|
+
const targetBehindDir = velHorizontal.clone().normalize().negate();
|
|
1650
|
+
this.camBehindDir.lerp(targetBehindDir, this._camCollisionLerp).normalize();
|
|
1651
|
+
const camHeightOffset = v.size.h;
|
|
1652
|
+
const targetCamPos = lookTarget.clone().add(this.camBehindDir.clone().multiplyScalar(desiredDist)).add(new THREE3.Vector3(0, camHeightOffset, 0));
|
|
1653
|
+
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
1654
|
+
this.controls.update();
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
const vehicleUp = this.upVector.clone().applyQuaternion(vehicleGroup.quaternion);
|
|
1659
|
+
const angleWithUp = vehicleUp.angleTo(this.upVector);
|
|
1660
|
+
if (angleWithUp > Math.PI / 2) {
|
|
1661
|
+
const size = new THREE3.Vector3();
|
|
1662
|
+
v.vehicleBBox?.getSize(size);
|
|
1663
|
+
const translation2 = chassisBody.translation();
|
|
1664
|
+
chassisBody.setTranslation(new this.RAPIER.Vector3(translation2.x, translation2.y + size.y, translation2.z), true);
|
|
1665
|
+
chassisBody.setRotation(new this.RAPIER.Quaternion(0, 0, 0, 1), true);
|
|
1666
|
+
chassisBody.setLinvel(new this.RAPIER.Vector3(0, 0, 0), true);
|
|
1667
|
+
chassisBody.setAngvel(new this.RAPIER.Vector3(0, 0, 0), true);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* 更新所有车辆物理和位置
|
|
1672
|
+
*/
|
|
1673
|
+
updateVehicleInertia(delta) {
|
|
1674
|
+
if (!this.world) return;
|
|
1675
|
+
this.world.timestep = delta;
|
|
1676
|
+
this.world.step();
|
|
1677
|
+
for (const v of this.vehicles) {
|
|
1678
|
+
const { vehicleController, chassisBody, vehicleGroup, updateWheelVisuals } = v;
|
|
1679
|
+
vehicleController.updateVehicle(delta);
|
|
1680
|
+
const velocity = chassisBody.linvel();
|
|
1681
|
+
const currentSpeed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
|
|
1682
|
+
const maxSpeed = this.vehicleParams.power.maxSpeed * v.scale;
|
|
1683
|
+
if (currentSpeed > maxSpeed) {
|
|
1684
|
+
const s = maxSpeed / currentSpeed;
|
|
1685
|
+
chassisBody.setLinvel(new this.RAPIER.Vector3(velocity.x * s, velocity.y * s, velocity.z * s), true);
|
|
1686
|
+
}
|
|
1687
|
+
const translation = chassisBody.translation();
|
|
1688
|
+
const rotationSync = chassisBody.rotation();
|
|
1689
|
+
vehicleGroup.position.set(translation.x, translation.y, translation.z);
|
|
1690
|
+
vehicleGroup.quaternion.set(rotationSync.x, rotationSync.y, rotationSync.z, rotationSync.w);
|
|
1691
|
+
if (updateWheelVisuals) updateWheelVisuals();
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* 更新人物
|
|
1696
|
+
*/
|
|
1697
|
+
updatePlayer(delta) {
|
|
1698
|
+
if (this.isMovingToBoardingPoint) {
|
|
1699
|
+
this.updateMoveToBoardingPoint(delta);
|
|
1700
|
+
}
|
|
760
1701
|
if (!this.isFlying) {
|
|
761
1702
|
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
762
1703
|
}
|
|
@@ -787,32 +1728,24 @@ var PlayerController = class {
|
|
|
787
1728
|
const intersects = this._raycaster.intersectObject(this.collider, false);
|
|
788
1729
|
if (intersects.length > 0) {
|
|
789
1730
|
playerDistanceFromGround = this.player.position.y - intersects[0].point.y;
|
|
790
|
-
const normal = intersects[0].normal;
|
|
791
|
-
const angle2 = this.getAngleWithYAxis(normal) * 180 / Math.PI;
|
|
792
1731
|
const maxH = this.playerHeight * this.playerModel.scale * 0.9;
|
|
793
1732
|
const h = this.playerHeight * this.playerModel.scale * 0.75;
|
|
794
1733
|
const minH = this.playerHeight * this.playerModel.scale * 0.7;
|
|
795
1734
|
if (!this.isFlying) {
|
|
796
|
-
if (playerDistanceFromGround
|
|
1735
|
+
if (playerDistanceFromGround >= maxH) {
|
|
797
1736
|
this.playerVelocity.y += delta * this.gravity;
|
|
798
1737
|
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
799
1738
|
this.playerIsOnGround = false;
|
|
800
|
-
} else if (playerDistanceFromGround
|
|
801
|
-
if (
|
|
802
|
-
this.playerVelocity.
|
|
803
|
-
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
1739
|
+
} else if (playerDistanceFromGround >= h && playerDistanceFromGround < maxH) {
|
|
1740
|
+
if (!this.spacePressed) {
|
|
1741
|
+
this.playerVelocity.set(0, 0, 0);
|
|
804
1742
|
this.playerIsOnGround = true;
|
|
805
|
-
|
|
806
|
-
if (this.spacePressed) {
|
|
807
|
-
this.playerVelocity.y += delta * this.gravity;
|
|
808
|
-
} else {
|
|
809
|
-
this.playerVelocity.set(0, 0, 0);
|
|
810
|
-
this.playerIsOnGround = true;
|
|
811
|
-
}
|
|
1743
|
+
this.player.position.y = intersects[0].point.y + h;
|
|
812
1744
|
}
|
|
813
|
-
} else if (playerDistanceFromGround
|
|
1745
|
+
} else if (playerDistanceFromGround >= minH && playerDistanceFromGround < h) {
|
|
814
1746
|
this.playerVelocity.set(0, 0, 0);
|
|
815
1747
|
this.playerIsOnGround = true;
|
|
1748
|
+
this.player.position.y = intersects[0].point.y + h;
|
|
816
1749
|
} else if (playerDistanceFromGround < minH) {
|
|
817
1750
|
this.playerVelocity.set(0, 0, 0);
|
|
818
1751
|
this.player.position.set(this.player.position.x, intersects[0].point.y + h, this.player.position.z);
|
|
@@ -830,23 +1763,38 @@ var PlayerController = class {
|
|
|
830
1763
|
this.tempBox.expandByPoint(this.tempSegment.start);
|
|
831
1764
|
this.tempBox.expandByPoint(this.tempSegment.end);
|
|
832
1765
|
this.tempBox.expandByScalar(capsuleInfo.radius);
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1766
|
+
if (!this.isMovingToBoardingPoint) {
|
|
1767
|
+
this.collider?.geometry?.boundsTree?.shapecast({
|
|
1768
|
+
intersectsBounds: (box) => box.intersectsBox(this.tempBox),
|
|
1769
|
+
intersectsTriangle: (tri) => {
|
|
1770
|
+
const triPoint = this.tempVector;
|
|
1771
|
+
const capsulePoint = this.tempVector2;
|
|
1772
|
+
const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint);
|
|
1773
|
+
if (distance < capsuleInfo.radius) {
|
|
1774
|
+
const normal = tri.getNormal(new THREE3.Vector3());
|
|
1775
|
+
if (normal.y > 0.5 && !this.isFlying) return;
|
|
1776
|
+
const depth = capsuleInfo.radius - distance;
|
|
1777
|
+
const direction = capsulePoint.sub(triPoint).normalize();
|
|
1778
|
+
this.tempSegment.start.addScaledVector(direction, depth);
|
|
1779
|
+
this.tempSegment.end.addScaledVector(direction, depth);
|
|
1780
|
+
}
|
|
847
1781
|
}
|
|
848
|
-
}
|
|
849
|
-
|
|
1782
|
+
});
|
|
1783
|
+
this.dynamicCollider?.geometry?.boundsTree?.shapecast({
|
|
1784
|
+
intersectsBounds: (box) => box.intersectsBox(this.tempBox),
|
|
1785
|
+
intersectsTriangle: (tri) => {
|
|
1786
|
+
const triPoint = this.tempVector;
|
|
1787
|
+
const capsulePoint = this.tempVector2;
|
|
1788
|
+
const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint);
|
|
1789
|
+
if (distance < capsuleInfo.radius) {
|
|
1790
|
+
const depth = capsuleInfo.radius - distance;
|
|
1791
|
+
const direction = capsulePoint.sub(triPoint).normalize();
|
|
1792
|
+
this.tempSegment.start.addScaledVector(direction, depth);
|
|
1793
|
+
this.tempSegment.end.addScaledVector(direction, depth);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
850
1798
|
const newPosition = this.tempVector.copy(this.tempSegment.start).applyMatrix4(this.collider.matrixWorld);
|
|
851
1799
|
const deltaVector = this.tempVector2.subVectors(newPosition, this.player.position);
|
|
852
1800
|
const offset = Math.max(0, deltaVector.length() - 1e-5);
|
|
@@ -879,34 +1827,36 @@ var PlayerController = class {
|
|
|
879
1827
|
}
|
|
880
1828
|
}
|
|
881
1829
|
if (this.isFlying) {
|
|
882
|
-
this.
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1830
|
+
if (!this.isFirstPerson) {
|
|
1831
|
+
this.camDir.y = 0;
|
|
1832
|
+
this.camDir.normalize();
|
|
1833
|
+
this.camDir.negate();
|
|
1834
|
+
this.moveDir.normalize();
|
|
1835
|
+
this.moveDir.negate();
|
|
1836
|
+
const lookTarget = this.player.position.clone().add(this.fwdPressed ? this.moveDir : this.camDir);
|
|
1837
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
1838
|
+
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
1839
|
+
const alpha = Math.min(1, this.rotationSpeed * delta);
|
|
1840
|
+
this.player.quaternion.slerp(this.targetQuat, alpha);
|
|
1841
|
+
}
|
|
892
1842
|
}
|
|
893
1843
|
if (!this.isFirstPerson) {
|
|
894
1844
|
const lookTarget = this.player.position.clone();
|
|
895
|
-
lookTarget.y +=
|
|
1845
|
+
lookTarget.y += this.playerHeight / 6 * this.playerModel.scale;
|
|
896
1846
|
this.camera.position.sub(this.controls.target);
|
|
897
1847
|
this.controls.target.copy(lookTarget);
|
|
898
1848
|
this.camera.position.add(lookTarget);
|
|
899
1849
|
this.controls.update();
|
|
900
1850
|
if (!this.enableZoom) {
|
|
901
1851
|
this._personToCam.subVectors(this.camera.position, this.player.position);
|
|
902
|
-
const origin = this.player.position.clone()
|
|
1852
|
+
const origin = this.player.position.clone();
|
|
903
1853
|
const direction = this._personToCam.clone().normalize();
|
|
904
1854
|
const desiredDist = this._personToCam.length();
|
|
905
1855
|
this._raycasterPersonToCam.set(origin, direction);
|
|
906
1856
|
this._raycasterPersonToCam.far = desiredDist;
|
|
907
|
-
const
|
|
908
|
-
if (
|
|
909
|
-
const hit =
|
|
1857
|
+
const intersectsCamera = this._raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1858
|
+
if (intersectsCamera.length > 0) {
|
|
1859
|
+
const hit = intersectsCamera[0];
|
|
910
1860
|
const safeDist = Math.max(hit.distance - this._camEpsilon, this._minCamDistance);
|
|
911
1861
|
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
912
1862
|
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
@@ -926,13 +1876,13 @@ var PlayerController = class {
|
|
|
926
1876
|
if (this.player.position.y < this.boundingBoxMinY - 1) {
|
|
927
1877
|
this._originTmp.set(this.player.position.x, 1e4, this.player.position.z);
|
|
928
1878
|
this._raycaster.ray.origin.copy(this._originTmp);
|
|
929
|
-
const
|
|
930
|
-
if (
|
|
1879
|
+
const intersectsFall = this._raycaster.intersectObject(this.collider, false);
|
|
1880
|
+
if (intersectsFall.length > 0) {
|
|
931
1881
|
console.log("\u73A9\u5BB6\u4E3Abug\u610F\u5916\u6389\u843D");
|
|
932
|
-
this.reset(new
|
|
1882
|
+
this.reset(new THREE3.Vector3(this.player.position.x, intersectsFall[0].point.y + 5, this.player.position.z));
|
|
933
1883
|
} else {
|
|
934
1884
|
console.log("\u73A9\u5BB6\u6B63\u5E38\u6389\u843D");
|
|
935
|
-
this.reset(new
|
|
1885
|
+
this.reset(new THREE3.Vector3(this.player.position.x, this.player.position.y + 15, this.player.position.z));
|
|
936
1886
|
}
|
|
937
1887
|
}
|
|
938
1888
|
}
|
|
@@ -941,26 +1891,19 @@ var PlayerController = class {
|
|
|
941
1891
|
*/
|
|
942
1892
|
updateMixers(delta) {
|
|
943
1893
|
if (this.personMixer) this.personMixer.update(delta);
|
|
944
|
-
|
|
1894
|
+
for (const v of this.vehicles) {
|
|
1895
|
+
v.vehicleMixer?.update(delta);
|
|
1896
|
+
}
|
|
945
1897
|
}
|
|
946
|
-
/**
|
|
947
|
-
* 重置玩家位置
|
|
948
|
-
*/
|
|
949
1898
|
reset(position) {
|
|
950
1899
|
if (!this.player) return;
|
|
951
1900
|
this.playerVelocity.set(0, 0, 0);
|
|
952
1901
|
this.player.position.copy(position ?? this.initPos);
|
|
953
1902
|
}
|
|
954
|
-
/**
|
|
955
|
-
* 获取玩家位置
|
|
956
|
-
*/
|
|
957
1903
|
getPosition() {
|
|
958
1904
|
return this.player.position;
|
|
959
1905
|
}
|
|
960
1906
|
// ==================== 输入处理 ====================
|
|
961
|
-
/**
|
|
962
|
-
* 设置输入
|
|
963
|
-
*/
|
|
964
1907
|
setInput(input) {
|
|
965
1908
|
if (typeof input.moveX === "number") {
|
|
966
1909
|
this.lftPressed = input.moveX === -1;
|
|
@@ -1000,9 +1943,6 @@ var PlayerController = class {
|
|
|
1000
1943
|
}
|
|
1001
1944
|
}
|
|
1002
1945
|
}
|
|
1003
|
-
/**
|
|
1004
|
-
* 事件绑定
|
|
1005
|
-
*/
|
|
1006
1946
|
onAllEvent() {
|
|
1007
1947
|
this.isupdate = true;
|
|
1008
1948
|
this.setPointerLock();
|
|
@@ -1011,9 +1951,6 @@ var PlayerController = class {
|
|
|
1011
1951
|
window.addEventListener("mousemove", this._mouseMove);
|
|
1012
1952
|
window.addEventListener("click", this._mouseClick);
|
|
1013
1953
|
}
|
|
1014
|
-
/**
|
|
1015
|
-
* 事件解绑
|
|
1016
|
-
*/
|
|
1017
1954
|
offAllEvent() {
|
|
1018
1955
|
this.isupdate = false;
|
|
1019
1956
|
document.exitPointerLock();
|
|
@@ -1022,9 +1959,6 @@ var PlayerController = class {
|
|
|
1022
1959
|
window.removeEventListener("mousemove", this._mouseMove);
|
|
1023
1960
|
window.removeEventListener("click", this._mouseClick);
|
|
1024
1961
|
}
|
|
1025
|
-
/**
|
|
1026
|
-
* 初始化移动端摇杆控制
|
|
1027
|
-
*/
|
|
1028
1962
|
async initMobileControls() {
|
|
1029
1963
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
1030
1964
|
this.controls.touches = { ONE: null, TWO: null };
|
|
@@ -1075,9 +2009,7 @@ var PlayerController = class {
|
|
|
1075
2009
|
const sprintThreshold = JOY_SIZE / 2;
|
|
1076
2010
|
const isSprinting = distance >= sprintThreshold;
|
|
1077
2011
|
const prev = this.prevJoyState || { dirX: 0, dirY: 0, shift: false };
|
|
1078
|
-
if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift)
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
2012
|
+
if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift) return;
|
|
1081
2013
|
this.prevJoyState = { dirX, dirY, shift: isSprinting };
|
|
1082
2014
|
this.setInput({ moveX: dirX, moveY: dirY, shift: isSprinting });
|
|
1083
2015
|
});
|
|
@@ -1189,9 +2121,6 @@ var PlayerController = class {
|
|
|
1189
2121
|
{ passive: false }
|
|
1190
2122
|
);
|
|
1191
2123
|
}
|
|
1192
|
-
/**
|
|
1193
|
-
* 销毁移动端摇杆控制
|
|
1194
|
-
*/
|
|
1195
2124
|
destroyMobileControls() {
|
|
1196
2125
|
try {
|
|
1197
2126
|
if (this.joystickManager && this.joystickManager.destroy) {
|
|
@@ -1227,9 +2156,6 @@ var PlayerController = class {
|
|
|
1227
2156
|
}
|
|
1228
2157
|
}
|
|
1229
2158
|
// ==================== 销毁 ====================
|
|
1230
|
-
/**
|
|
1231
|
-
* 销毁人物控制器
|
|
1232
|
-
*/
|
|
1233
2159
|
destroy() {
|
|
1234
2160
|
this.offAllEvent();
|
|
1235
2161
|
if (this.player) {
|
|
@@ -1251,6 +2177,12 @@ var PlayerController = class {
|
|
|
1251
2177
|
this.collider = null;
|
|
1252
2178
|
}
|
|
1253
2179
|
this.destroyMobileControls();
|
|
2180
|
+
for (const v of this.vehicles) {
|
|
2181
|
+
this.scene.remove(v.vehicleGroup);
|
|
2182
|
+
v.pathPlanner?.dispose();
|
|
2183
|
+
}
|
|
2184
|
+
this.vehicles = [];
|
|
2185
|
+
this.activeVehicle = null;
|
|
1254
2186
|
controllerInstance = null;
|
|
1255
2187
|
}
|
|
1256
2188
|
};
|
|
@@ -1259,13 +2191,16 @@ function playerController() {
|
|
|
1259
2191
|
const c = controllerInstance;
|
|
1260
2192
|
return {
|
|
1261
2193
|
init: (opts, callback) => c.init(opts, callback),
|
|
2194
|
+
loadVehicleModel: (params) => c.loadVehicleModel(params),
|
|
1262
2195
|
changeView: () => c.changeView(),
|
|
1263
2196
|
reset: (pos) => c.reset(pos),
|
|
1264
2197
|
update: (dt) => c.update(dt),
|
|
1265
2198
|
destroy: () => c.destroy(),
|
|
1266
2199
|
setInput: (i) => c.setInput(i),
|
|
1267
2200
|
getposition: () => c.getPosition(),
|
|
1268
|
-
|
|
2201
|
+
getPerson: () => c.person,
|
|
2202
|
+
getActiveVehicle: () => c.activeVehicle,
|
|
2203
|
+
getAllVehicles: () => c.vehicles
|
|
1269
2204
|
};
|
|
1270
2205
|
}
|
|
1271
2206
|
function onAllEvent() {
|