three-player-controller 0.3.2 → 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 +57 -1
- package/dist/index.d.mts +103 -12
- package/dist/index.d.ts +103 -12
- package/dist/index.js +6318 -422
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1272 -330
- 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,18 +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();
|
|
26
|
-
|
|
292
|
+
this.controllerMode = 0;
|
|
293
|
+
// 0: 人物 1: 车辆
|
|
294
|
+
this.isChangeControllerTransitionTimer = null;
|
|
27
295
|
// ==================== 玩家基本属性 ====================
|
|
28
296
|
this.playerRadius = 45;
|
|
29
297
|
this.playerHeight = 180;
|
|
298
|
+
// 玩家参考身高
|
|
30
299
|
this.isFirstPerson = false;
|
|
31
300
|
this.boundingBoxMinY = 0;
|
|
32
301
|
// ==================== 测试参数 ====================
|
|
@@ -37,7 +306,59 @@ var PlayerController = class {
|
|
|
37
306
|
this.collider = null;
|
|
38
307
|
this.visualizer = null;
|
|
39
308
|
this.person = null;
|
|
40
|
-
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;
|
|
41
362
|
// ==================== 状态开关 ====================
|
|
42
363
|
this.playerIsOnGround = false;
|
|
43
364
|
this.isupdate = true;
|
|
@@ -65,46 +386,63 @@ var PlayerController = class {
|
|
|
65
386
|
this.lastTouchY = 0;
|
|
66
387
|
// ==================== 第三人称相机参数 ====================
|
|
67
388
|
this._camCollisionLerp = 0.18;
|
|
68
|
-
// 平滑系数
|
|
69
389
|
this._camEpsilon = 0.35;
|
|
70
|
-
// 摄像机与障碍物之间的安全距离
|
|
71
390
|
this._minCamDistance = 1;
|
|
72
|
-
// 摄像机最小距离
|
|
73
391
|
this._maxCamDistance = 4.4;
|
|
74
|
-
// 摄像机最大距离
|
|
75
392
|
this.orginMaxCamDistance = 4.4;
|
|
76
393
|
// ==================== 物理/运动 ====================
|
|
77
|
-
this.playerVelocity = new
|
|
78
|
-
|
|
79
|
-
this.upVector = new THREE.Vector3(0, 1, 0);
|
|
394
|
+
this.playerVelocity = new THREE3.Vector3();
|
|
395
|
+
this.upVector = new THREE3.Vector3(0, 1, 0);
|
|
80
396
|
// ==================== 临时复用向量/矩阵 ====================
|
|
81
|
-
this.tempVector = new
|
|
82
|
-
this.tempVector2 = new
|
|
83
|
-
this.tempBox = new
|
|
84
|
-
this.tempMat = new
|
|
85
|
-
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();
|
|
86
402
|
this.recheckAnimTimer = null;
|
|
87
403
|
// ==================== 相机朝向/移动复用向量 ====================
|
|
88
|
-
this.camDir = new
|
|
89
|
-
this.moveDir = new
|
|
90
|
-
this.targetQuat = new
|
|
91
|
-
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();
|
|
92
408
|
this.rotationSpeed = 10;
|
|
93
|
-
this.DIR_FWD = new
|
|
94
|
-
this.DIR_BKD = new
|
|
95
|
-
this.DIR_LFT = new
|
|
96
|
-
this.DIR_RGT = new
|
|
97
|
-
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);
|
|
98
414
|
// ==================== 射线检测 ====================
|
|
99
|
-
this._personToCam = new
|
|
100
|
-
this._originTmp = new
|
|
101
|
-
this._raycaster = new
|
|
102
|
-
this._raycasterPersonToCam = new
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
};
|
|
106
430
|
this.setAnimationByPressed = () => {
|
|
107
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
|
+
}
|
|
108
446
|
if (this.isFlying) {
|
|
109
447
|
if (!this.fwdPressed) {
|
|
110
448
|
this.playPersonAnimationByName("flyidle");
|
|
@@ -149,31 +487,33 @@ var PlayerController = class {
|
|
|
149
487
|
}, 200);
|
|
150
488
|
};
|
|
151
489
|
// ==================== 事件处理 ====================
|
|
152
|
-
/**
|
|
153
|
-
* 键盘按下事件
|
|
154
|
-
*/
|
|
155
490
|
this._boundOnKeydown = async (e) => {
|
|
156
491
|
if (e.ctrlKey && ["KeyW", "KeyA", "KeyS", "KeyD"].includes(e.code)) {
|
|
157
492
|
e.preventDefault();
|
|
158
493
|
}
|
|
159
494
|
switch (e.code) {
|
|
160
495
|
case "KeyW":
|
|
496
|
+
case "ArrowUp":
|
|
161
497
|
this.fwdPressed = true;
|
|
162
498
|
this.setAnimationByPressed();
|
|
163
499
|
break;
|
|
164
500
|
case "KeyS":
|
|
501
|
+
case "ArrowDown":
|
|
165
502
|
this.bkdPressed = true;
|
|
166
503
|
this.setAnimationByPressed();
|
|
167
504
|
break;
|
|
168
505
|
case "KeyD":
|
|
506
|
+
case "ArrowRight":
|
|
169
507
|
this.rgtPressed = true;
|
|
170
508
|
this.setAnimationByPressed();
|
|
171
509
|
break;
|
|
172
510
|
case "KeyA":
|
|
511
|
+
case "ArrowLeft":
|
|
173
512
|
this.lftPressed = true;
|
|
174
513
|
this.setAnimationByPressed();
|
|
175
514
|
break;
|
|
176
515
|
case "ShiftLeft":
|
|
516
|
+
case "ShiftRight":
|
|
177
517
|
this.shiftPressed = true;
|
|
178
518
|
this.setAnimationByPressed();
|
|
179
519
|
this.controls.mouseButtons = { LEFT: 2, MIDDLE: 1, RIGHT: 0 };
|
|
@@ -194,6 +534,7 @@ var PlayerController = class {
|
|
|
194
534
|
this.changeView();
|
|
195
535
|
break;
|
|
196
536
|
case "KeyF":
|
|
537
|
+
if (this.controllerMode == 1) return;
|
|
197
538
|
this.isFlying = !this.isFlying;
|
|
198
539
|
this.setAnimationByPressed();
|
|
199
540
|
if (!this.isFlying && !this.playerIsOnGround) {
|
|
@@ -201,32 +542,39 @@ var PlayerController = class {
|
|
|
201
542
|
}
|
|
202
543
|
break;
|
|
203
544
|
case "KeyE":
|
|
204
|
-
this.
|
|
545
|
+
if (this.isFlying) return;
|
|
546
|
+
if (this.controllerMode == 0) {
|
|
547
|
+
this.enterVehicle();
|
|
548
|
+
} else {
|
|
549
|
+
this.exitVehicle();
|
|
550
|
+
}
|
|
205
551
|
break;
|
|
206
552
|
}
|
|
207
553
|
};
|
|
208
|
-
/**
|
|
209
|
-
* 键盘抬起事件
|
|
210
|
-
*/
|
|
211
554
|
this._boundOnKeyup = (e) => {
|
|
212
555
|
switch (e.code) {
|
|
213
556
|
case "KeyW":
|
|
557
|
+
case "ArrowUp":
|
|
214
558
|
this.fwdPressed = false;
|
|
215
559
|
this.setAnimationByPressed();
|
|
216
560
|
break;
|
|
217
561
|
case "KeyS":
|
|
562
|
+
case "ArrowDown":
|
|
218
563
|
this.bkdPressed = false;
|
|
219
564
|
this.setAnimationByPressed();
|
|
220
565
|
break;
|
|
221
566
|
case "KeyD":
|
|
567
|
+
case "ArrowRight":
|
|
222
568
|
this.rgtPressed = false;
|
|
223
569
|
this.setAnimationByPressed();
|
|
224
570
|
break;
|
|
225
571
|
case "KeyA":
|
|
572
|
+
case "ArrowLeft":
|
|
226
573
|
this.lftPressed = false;
|
|
227
574
|
this.setAnimationByPressed();
|
|
228
575
|
break;
|
|
229
576
|
case "ShiftLeft":
|
|
577
|
+
case "ShiftRight":
|
|
230
578
|
this.shiftPressed = false;
|
|
231
579
|
this.setAnimationByPressed();
|
|
232
580
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
@@ -239,23 +587,14 @@ var PlayerController = class {
|
|
|
239
587
|
break;
|
|
240
588
|
}
|
|
241
589
|
};
|
|
242
|
-
/**
|
|
243
|
-
* 鼠标移动事件
|
|
244
|
-
*/
|
|
245
590
|
this._mouseMove = (e) => {
|
|
246
591
|
if (document.pointerLockElement !== document.body) return;
|
|
247
592
|
this.setToward(e.movementX, e.movementY, 1e-4);
|
|
248
593
|
};
|
|
249
|
-
|
|
250
|
-
* 鼠标点击事件
|
|
251
|
-
*/
|
|
252
|
-
this._mouseClick = (e) => {
|
|
594
|
+
this._mouseClick = (_e) => {
|
|
253
595
|
this.setPointerLock();
|
|
254
596
|
};
|
|
255
597
|
// ==================== 移动端控制 ====================
|
|
256
|
-
/**
|
|
257
|
-
* 指针按下事件
|
|
258
|
-
*/
|
|
259
598
|
this.onPointerDown = (e) => {
|
|
260
599
|
if (e.pointerType !== "touch") return;
|
|
261
600
|
this.isLookDown = true;
|
|
@@ -265,9 +604,6 @@ var PlayerController = class {
|
|
|
265
604
|
this.lookAreaEl?.setPointerCapture?.(e.pointerId);
|
|
266
605
|
e.preventDefault();
|
|
267
606
|
};
|
|
268
|
-
/**
|
|
269
|
-
* 指针移动事件
|
|
270
|
-
*/
|
|
271
607
|
this.onPointerMove = (e) => {
|
|
272
608
|
if (!this.isLookDown || e.pointerId !== this.lookPointerId) return;
|
|
273
609
|
const dx = e.clientX - this.lastTouchX;
|
|
@@ -277,9 +613,6 @@ var PlayerController = class {
|
|
|
277
613
|
this.setInput({ lookDeltaX: dx, lookDeltaY: dy });
|
|
278
614
|
e.preventDefault();
|
|
279
615
|
};
|
|
280
|
-
/**
|
|
281
|
-
* 指针抬起事件
|
|
282
|
-
*/
|
|
283
616
|
this.onPointerUp = (e) => {
|
|
284
617
|
if (e.pointerId !== this.lookPointerId) return;
|
|
285
618
|
this.isLookDown = false;
|
|
@@ -290,19 +623,15 @@ var PlayerController = class {
|
|
|
290
623
|
this._raycasterPersonToCam.firstHitOnly = true;
|
|
291
624
|
}
|
|
292
625
|
// ==================== 初始化相关方法 ====================
|
|
293
|
-
/**
|
|
294
|
-
* 初始化控制器
|
|
295
|
-
*/
|
|
296
626
|
async init(opts, callback) {
|
|
297
627
|
this.scene = opts.scene;
|
|
298
628
|
this.camera = opts.camera;
|
|
299
629
|
this.camera.rotation.order = "YXZ";
|
|
300
630
|
this.controls = opts.controls;
|
|
301
631
|
this.playerModel = opts.playerModel;
|
|
302
|
-
this.initPos = opts.initPos ?? new
|
|
632
|
+
this.initPos = opts.initPos ?? new THREE3.Vector3(0, 0, 0);
|
|
303
633
|
this.mouseSensity = opts.mouseSensity ?? 5;
|
|
304
634
|
const s = this.playerModel.scale;
|
|
305
|
-
this.visualizeDepth = 0 * s;
|
|
306
635
|
this.gravity = (opts.playerModel.gravity ?? -2400) * s;
|
|
307
636
|
this.jumpHeight = (opts.playerModel.jumpHeight ?? 800) * s;
|
|
308
637
|
this.originPlayerSpeed = (opts.playerModel.speed ?? 400) * s;
|
|
@@ -314,52 +643,104 @@ var PlayerController = class {
|
|
|
314
643
|
this._maxCamDistance = (opts.maxCamDistance ?? 440) * s;
|
|
315
644
|
this.orginMaxCamDistance = this._maxCamDistance;
|
|
316
645
|
this.thirdMouseMode = opts.thirdMouseMode ?? 1;
|
|
646
|
+
this.enableZoom = opts.enableZoom ?? false;
|
|
317
647
|
const isMobileDevice = () => navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
318
648
|
this.isShowMobileControls = (opts.isShowMobileControls ?? true) && isMobileDevice();
|
|
319
649
|
if (this.isShowMobileControls) {
|
|
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,135 +831,467 @@ 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);
|
|
869
|
+
}
|
|
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}`);
|
|
511
880
|
}
|
|
512
|
-
|
|
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
|
+
}
|
|
518
1009
|
/**
|
|
519
|
-
*
|
|
1010
|
+
* 为指定车辆创建障碍物检测器
|
|
520
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
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* 完成上车
|
|
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);
|
|
1242
|
+
this.setFirstPersonCamera();
|
|
527
1243
|
} else {
|
|
528
1244
|
this.scene.attach(this.camera);
|
|
529
1245
|
const worldPos = this.player.position.clone();
|
|
530
|
-
const dir = new
|
|
1246
|
+
const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
|
|
531
1247
|
const angle = Math.atan2(dir.z, dir.x);
|
|
532
|
-
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);
|
|
533
1249
|
this.camera.position.copy(worldPos).add(offset);
|
|
534
1250
|
this.controls.target.copy(worldPos);
|
|
1251
|
+
this.controls.enableZoom = this.enableZoom;
|
|
535
1252
|
}
|
|
536
1253
|
this.setPointerLock();
|
|
537
1254
|
}
|
|
538
|
-
|
|
539
|
-
this.
|
|
540
|
-
|
|
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;
|
|
541
1265
|
}
|
|
542
|
-
/**
|
|
543
|
-
* 设置指针锁定
|
|
544
|
-
*/
|
|
545
1266
|
setPointerLock() {
|
|
546
1267
|
if ((this.thirdMouseMode === 0 || this.thirdMouseMode === 1) && !this.isFirstPerson || this.isFirstPerson) {
|
|
547
1268
|
document.body.requestPointerLock();
|
|
1269
|
+
} else {
|
|
1270
|
+
document.exitPointerLock();
|
|
548
1271
|
}
|
|
549
1272
|
}
|
|
550
|
-
/**
|
|
551
|
-
* 设置摄像机初始位置
|
|
552
|
-
*/
|
|
553
1273
|
setCameraPos() {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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);
|
|
564
1288
|
}
|
|
565
|
-
/**
|
|
566
|
-
* 设置控制器
|
|
567
|
-
*/
|
|
568
1289
|
setControls() {
|
|
569
|
-
this.controls.
|
|
1290
|
+
this.controls.enableZoom = this.enableZoom;
|
|
570
1291
|
this.controls.rotateSpeed = this.mouseSensity * 0.05;
|
|
571
1292
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
1293
|
+
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
572
1294
|
}
|
|
573
|
-
/**
|
|
574
|
-
* 重置控制器
|
|
575
|
-
*/
|
|
576
1295
|
resetControls() {
|
|
577
1296
|
if (!this.controls) return;
|
|
578
1297
|
this.controls.enabled = true;
|
|
@@ -582,38 +1301,60 @@ var PlayerController = class {
|
|
|
582
1301
|
this.controls.enableZoom = true;
|
|
583
1302
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
584
1303
|
}
|
|
585
|
-
/**
|
|
586
|
-
* 设置朝向
|
|
587
|
-
*/
|
|
588
1304
|
setToward(dx, dy, speed) {
|
|
589
|
-
if (this.
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
+
}
|
|
594
1330
|
} else {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
+
}
|
|
611
1356
|
}
|
|
612
1357
|
}
|
|
613
|
-
// ==================== 物理与碰撞检测 ====================
|
|
614
|
-
/**
|
|
615
|
-
* 统一属性集合
|
|
616
|
-
*/
|
|
617
1358
|
unifiedAttribute(collected) {
|
|
618
1359
|
const attrMap = /* @__PURE__ */ new Map();
|
|
619
1360
|
const attrConflict = /* @__PURE__ */ new Set();
|
|
@@ -660,28 +1401,14 @@ var PlayerController = class {
|
|
|
660
1401
|
const meta = attrMap.get(name);
|
|
661
1402
|
const len = count * meta.itemSize;
|
|
662
1403
|
const array = new meta.arrayCtor(len);
|
|
663
|
-
g.setAttribute(name, new
|
|
1404
|
+
g.setAttribute(name, new THREE3.BufferAttribute(array, meta.itemSize, meta.normalized));
|
|
664
1405
|
}
|
|
665
1406
|
}
|
|
666
1407
|
}
|
|
667
1408
|
return collected;
|
|
668
1409
|
}
|
|
669
|
-
/**
|
|
670
|
-
* BVH碰撞体构建
|
|
671
|
-
*/
|
|
672
1410
|
async createBVH(meshUrl = "") {
|
|
673
1411
|
await this.initLoader();
|
|
674
|
-
const ensureAttributesMinimal = (geom) => {
|
|
675
|
-
if (!geom.attributes.position) return null;
|
|
676
|
-
if (!geom.attributes.normal) geom.computeVertexNormals();
|
|
677
|
-
if (!geom.attributes.uv) {
|
|
678
|
-
const count = geom.attributes.position.count;
|
|
679
|
-
const dummyUV = new Float32Array(count * 2);
|
|
680
|
-
geom.setAttribute("uv", new THREE.BufferAttribute(dummyUV, 2));
|
|
681
|
-
}
|
|
682
|
-
return geom;
|
|
683
|
-
};
|
|
684
|
-
let collected = [];
|
|
685
1412
|
if (meshUrl === "") {
|
|
686
1413
|
if (this.collider) {
|
|
687
1414
|
this.scene.remove(this.collider);
|
|
@@ -694,15 +1421,15 @@ var PlayerController = class {
|
|
|
694
1421
|
let geom = mesh.geometry.clone();
|
|
695
1422
|
geom.applyMatrix4(mesh.matrixWorld);
|
|
696
1423
|
if (geom.index) geom = geom.toNonIndexed();
|
|
697
|
-
const safe = ensureAttributesMinimal(geom);
|
|
698
|
-
if (safe) collected.push(safe);
|
|
1424
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1425
|
+
if (safe) this.collected.push(safe);
|
|
699
1426
|
} catch (e) {
|
|
700
1427
|
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
701
1428
|
}
|
|
702
1429
|
}
|
|
703
1430
|
});
|
|
704
|
-
if (!collected.length) return;
|
|
705
|
-
collected = this.unifiedAttribute(collected);
|
|
1431
|
+
if (!this.collected.length) return;
|
|
1432
|
+
this.collected = this.unifiedAttribute(this.collected);
|
|
706
1433
|
} else {
|
|
707
1434
|
const gltf = await this.loader.loadAsync(meshUrl);
|
|
708
1435
|
const mesh = gltf.scene.children[0];
|
|
@@ -710,34 +1437,73 @@ var PlayerController = class {
|
|
|
710
1437
|
let geom = mesh.geometry.clone();
|
|
711
1438
|
geom.applyMatrix4(mesh.matrixWorld);
|
|
712
1439
|
if (geom.index) geom = geom.toNonIndexed();
|
|
713
|
-
const safe = ensureAttributesMinimal(geom);
|
|
714
|
-
if (safe) collected.push(safe);
|
|
1440
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1441
|
+
if (safe) this.collected.push(safe);
|
|
715
1442
|
}
|
|
716
|
-
const merged = BufferGeometryUtils.mergeGeometries(collected, false);
|
|
1443
|
+
const merged = BufferGeometryUtils.mergeGeometries(this.collected, false);
|
|
717
1444
|
if (!merged) {
|
|
718
1445
|
console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
|
|
719
1446
|
return;
|
|
720
1447
|
}
|
|
721
1448
|
merged.boundsTree = new MeshBVH(merged, { maxDepth: 100 });
|
|
722
|
-
this.collider = new
|
|
1449
|
+
this.collider = new THREE3.Mesh(
|
|
723
1450
|
merged,
|
|
724
|
-
new
|
|
1451
|
+
new THREE3.MeshBasicMaterial({
|
|
725
1452
|
opacity: 0.5,
|
|
726
1453
|
transparent: true,
|
|
727
|
-
wireframe: true
|
|
1454
|
+
wireframe: true,
|
|
1455
|
+
depthTest: true
|
|
728
1456
|
})
|
|
729
1457
|
);
|
|
730
1458
|
if (this.displayCollider) this.scene.add(this.collider);
|
|
731
1459
|
if (this.displayVisualizer) {
|
|
732
1460
|
if (this.visualizer) this.scene.remove(this.visualizer);
|
|
733
|
-
this.visualizer = new MeshBVHHelper(this.collider,
|
|
1461
|
+
this.visualizer = new MeshBVHHelper(this.collider, 0);
|
|
734
1462
|
this.scene.add(this.visualizer);
|
|
735
1463
|
}
|
|
736
1464
|
this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
|
|
737
1465
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
+
}
|
|
741
1507
|
getAngleWithYAxis(normal) {
|
|
742
1508
|
const yAxis = { x: 0, y: 1, z: 0 };
|
|
743
1509
|
const dotProduct = normal.x * yAxis.x + normal.y * yAxis.y + normal.z * yAxis.z;
|
|
@@ -745,13 +1511,193 @@ var PlayerController = class {
|
|
|
745
1511
|
const cosTheta = dotProduct / normalMagnitude;
|
|
746
1512
|
return Math.acos(cosTheta);
|
|
747
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
|
+
}
|
|
748
1551
|
// ==================== 循环更新 ====================
|
|
749
|
-
/**
|
|
750
|
-
* 每帧更新
|
|
751
|
-
*/
|
|
752
1552
|
async update(delta = clock.getDelta()) {
|
|
753
1553
|
if (!this.isupdate || !this.player || !this.collider) return;
|
|
754
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
|
+
}
|
|
755
1701
|
if (!this.isFlying) {
|
|
756
1702
|
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
757
1703
|
}
|
|
@@ -782,32 +1728,24 @@ var PlayerController = class {
|
|
|
782
1728
|
const intersects = this._raycaster.intersectObject(this.collider, false);
|
|
783
1729
|
if (intersects.length > 0) {
|
|
784
1730
|
playerDistanceFromGround = this.player.position.y - intersects[0].point.y;
|
|
785
|
-
const normal = intersects[0].normal;
|
|
786
|
-
const angle2 = this.getAngleWithYAxis(normal) * 180 / Math.PI;
|
|
787
1731
|
const maxH = this.playerHeight * this.playerModel.scale * 0.9;
|
|
788
1732
|
const h = this.playerHeight * this.playerModel.scale * 0.75;
|
|
789
1733
|
const minH = this.playerHeight * this.playerModel.scale * 0.7;
|
|
790
1734
|
if (!this.isFlying) {
|
|
791
|
-
if (playerDistanceFromGround
|
|
1735
|
+
if (playerDistanceFromGround >= maxH) {
|
|
792
1736
|
this.playerVelocity.y += delta * this.gravity;
|
|
793
1737
|
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
794
1738
|
this.playerIsOnGround = false;
|
|
795
|
-
} else if (playerDistanceFromGround
|
|
796
|
-
if (
|
|
797
|
-
this.playerVelocity.
|
|
798
|
-
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);
|
|
799
1742
|
this.playerIsOnGround = true;
|
|
800
|
-
|
|
801
|
-
if (this.spacePressed) {
|
|
802
|
-
this.playerVelocity.y += delta * this.gravity;
|
|
803
|
-
} else {
|
|
804
|
-
this.playerVelocity.set(0, 0, 0);
|
|
805
|
-
this.playerIsOnGround = true;
|
|
806
|
-
}
|
|
1743
|
+
this.player.position.y = intersects[0].point.y + h;
|
|
807
1744
|
}
|
|
808
|
-
} else if (playerDistanceFromGround
|
|
1745
|
+
} else if (playerDistanceFromGround >= minH && playerDistanceFromGround < h) {
|
|
809
1746
|
this.playerVelocity.set(0, 0, 0);
|
|
810
1747
|
this.playerIsOnGround = true;
|
|
1748
|
+
this.player.position.y = intersects[0].point.y + h;
|
|
811
1749
|
} else if (playerDistanceFromGround < minH) {
|
|
812
1750
|
this.playerVelocity.set(0, 0, 0);
|
|
813
1751
|
this.player.position.set(this.player.position.x, intersects[0].point.y + h, this.player.position.z);
|
|
@@ -825,23 +1763,38 @@ var PlayerController = class {
|
|
|
825
1763
|
this.tempBox.expandByPoint(this.tempSegment.start);
|
|
826
1764
|
this.tempBox.expandByPoint(this.tempSegment.end);
|
|
827
1765
|
this.tempBox.expandByScalar(capsuleInfo.radius);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
+
}
|
|
842
1781
|
}
|
|
843
|
-
}
|
|
844
|
-
|
|
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
|
+
}
|
|
845
1798
|
const newPosition = this.tempVector.copy(this.tempSegment.start).applyMatrix4(this.collider.matrixWorld);
|
|
846
1799
|
const deltaVector = this.tempVector2.subVectors(newPosition, this.player.position);
|
|
847
1800
|
const offset = Math.max(0, deltaVector.length() - 1e-5);
|
|
@@ -874,58 +1827,62 @@ var PlayerController = class {
|
|
|
874
1827
|
}
|
|
875
1828
|
}
|
|
876
1829
|
if (this.isFlying) {
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
+
}
|
|
887
1842
|
}
|
|
888
1843
|
if (!this.isFirstPerson) {
|
|
889
1844
|
const lookTarget = this.player.position.clone();
|
|
890
|
-
lookTarget.y +=
|
|
1845
|
+
lookTarget.y += this.playerHeight / 6 * this.playerModel.scale;
|
|
891
1846
|
this.camera.position.sub(this.controls.target);
|
|
892
1847
|
this.controls.target.copy(lookTarget);
|
|
893
1848
|
this.camera.position.add(lookTarget);
|
|
894
1849
|
this.controls.update();
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1850
|
+
if (!this.enableZoom) {
|
|
1851
|
+
this._personToCam.subVectors(this.camera.position, this.player.position);
|
|
1852
|
+
const origin = this.player.position.clone();
|
|
1853
|
+
const direction = this._personToCam.clone().normalize();
|
|
1854
|
+
const desiredDist = this._personToCam.length();
|
|
1855
|
+
this._raycasterPersonToCam.set(origin, direction);
|
|
1856
|
+
this._raycasterPersonToCam.far = desiredDist;
|
|
1857
|
+
const intersectsCamera = this._raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1858
|
+
if (intersectsCamera.length > 0) {
|
|
1859
|
+
const hit = intersectsCamera[0];
|
|
1860
|
+
const safeDist = Math.max(hit.distance - this._camEpsilon, this._minCamDistance);
|
|
1861
|
+
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
1862
|
+
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
1863
|
+
} else {
|
|
1864
|
+
this._raycasterPersonToCam.far = this._maxCamDistance;
|
|
1865
|
+
const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1866
|
+
let safeDist = this._maxCamDistance;
|
|
1867
|
+
if (intersectsMaxDis.length) {
|
|
1868
|
+
const hitMax = intersectsMaxDis[0];
|
|
1869
|
+
safeDist = hitMax.distance - this._camEpsilon;
|
|
1870
|
+
}
|
|
1871
|
+
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
1872
|
+
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
914
1873
|
}
|
|
915
|
-
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
916
|
-
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
917
1874
|
}
|
|
918
1875
|
}
|
|
919
1876
|
if (this.player.position.y < this.boundingBoxMinY - 1) {
|
|
920
1877
|
this._originTmp.set(this.player.position.x, 1e4, this.player.position.z);
|
|
921
1878
|
this._raycaster.ray.origin.copy(this._originTmp);
|
|
922
|
-
const
|
|
923
|
-
if (
|
|
1879
|
+
const intersectsFall = this._raycaster.intersectObject(this.collider, false);
|
|
1880
|
+
if (intersectsFall.length > 0) {
|
|
924
1881
|
console.log("\u73A9\u5BB6\u4E3Abug\u610F\u5916\u6389\u843D");
|
|
925
|
-
this.reset(new
|
|
1882
|
+
this.reset(new THREE3.Vector3(this.player.position.x, intersectsFall[0].point.y + 5, this.player.position.z));
|
|
926
1883
|
} else {
|
|
927
1884
|
console.log("\u73A9\u5BB6\u6B63\u5E38\u6389\u843D");
|
|
928
|
-
this.reset(new
|
|
1885
|
+
this.reset(new THREE3.Vector3(this.player.position.x, this.player.position.y + 15, this.player.position.z));
|
|
929
1886
|
}
|
|
930
1887
|
}
|
|
931
1888
|
}
|
|
@@ -934,26 +1891,19 @@ var PlayerController = class {
|
|
|
934
1891
|
*/
|
|
935
1892
|
updateMixers(delta) {
|
|
936
1893
|
if (this.personMixer) this.personMixer.update(delta);
|
|
937
|
-
|
|
1894
|
+
for (const v of this.vehicles) {
|
|
1895
|
+
v.vehicleMixer?.update(delta);
|
|
1896
|
+
}
|
|
938
1897
|
}
|
|
939
|
-
/**
|
|
940
|
-
* 重置玩家位置
|
|
941
|
-
*/
|
|
942
1898
|
reset(position) {
|
|
943
1899
|
if (!this.player) return;
|
|
944
1900
|
this.playerVelocity.set(0, 0, 0);
|
|
945
1901
|
this.player.position.copy(position ?? this.initPos);
|
|
946
1902
|
}
|
|
947
|
-
/**
|
|
948
|
-
* 获取玩家位置
|
|
949
|
-
*/
|
|
950
1903
|
getPosition() {
|
|
951
1904
|
return this.player.position;
|
|
952
1905
|
}
|
|
953
1906
|
// ==================== 输入处理 ====================
|
|
954
|
-
/**
|
|
955
|
-
* 设置输入
|
|
956
|
-
*/
|
|
957
1907
|
setInput(input) {
|
|
958
1908
|
if (typeof input.moveX === "number") {
|
|
959
1909
|
this.lftPressed = input.moveX === -1;
|
|
@@ -993,9 +1943,6 @@ var PlayerController = class {
|
|
|
993
1943
|
}
|
|
994
1944
|
}
|
|
995
1945
|
}
|
|
996
|
-
/**
|
|
997
|
-
* 事件绑定
|
|
998
|
-
*/
|
|
999
1946
|
onAllEvent() {
|
|
1000
1947
|
this.isupdate = true;
|
|
1001
1948
|
this.setPointerLock();
|
|
@@ -1004,9 +1951,6 @@ var PlayerController = class {
|
|
|
1004
1951
|
window.addEventListener("mousemove", this._mouseMove);
|
|
1005
1952
|
window.addEventListener("click", this._mouseClick);
|
|
1006
1953
|
}
|
|
1007
|
-
/**
|
|
1008
|
-
* 事件解绑
|
|
1009
|
-
*/
|
|
1010
1954
|
offAllEvent() {
|
|
1011
1955
|
this.isupdate = false;
|
|
1012
1956
|
document.exitPointerLock();
|
|
@@ -1015,9 +1959,6 @@ var PlayerController = class {
|
|
|
1015
1959
|
window.removeEventListener("mousemove", this._mouseMove);
|
|
1016
1960
|
window.removeEventListener("click", this._mouseClick);
|
|
1017
1961
|
}
|
|
1018
|
-
/**
|
|
1019
|
-
* 初始化移动端摇杆控制
|
|
1020
|
-
*/
|
|
1021
1962
|
async initMobileControls() {
|
|
1022
1963
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
1023
1964
|
this.controls.touches = { ONE: null, TWO: null };
|
|
@@ -1068,9 +2009,7 @@ var PlayerController = class {
|
|
|
1068
2009
|
const sprintThreshold = JOY_SIZE / 2;
|
|
1069
2010
|
const isSprinting = distance >= sprintThreshold;
|
|
1070
2011
|
const prev = this.prevJoyState || { dirX: 0, dirY: 0, shift: false };
|
|
1071
|
-
if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift)
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
2012
|
+
if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift) return;
|
|
1074
2013
|
this.prevJoyState = { dirX, dirY, shift: isSprinting };
|
|
1075
2014
|
this.setInput({ moveX: dirX, moveY: dirY, shift: isSprinting });
|
|
1076
2015
|
});
|
|
@@ -1182,9 +2121,6 @@ var PlayerController = class {
|
|
|
1182
2121
|
{ passive: false }
|
|
1183
2122
|
);
|
|
1184
2123
|
}
|
|
1185
|
-
/**
|
|
1186
|
-
* 销毁移动端摇杆控制
|
|
1187
|
-
*/
|
|
1188
2124
|
destroyMobileControls() {
|
|
1189
2125
|
try {
|
|
1190
2126
|
if (this.joystickManager && this.joystickManager.destroy) {
|
|
@@ -1220,9 +2156,6 @@ var PlayerController = class {
|
|
|
1220
2156
|
}
|
|
1221
2157
|
}
|
|
1222
2158
|
// ==================== 销毁 ====================
|
|
1223
|
-
/**
|
|
1224
|
-
* 销毁人物控制器
|
|
1225
|
-
*/
|
|
1226
2159
|
destroy() {
|
|
1227
2160
|
this.offAllEvent();
|
|
1228
2161
|
if (this.player) {
|
|
@@ -1244,6 +2177,12 @@ var PlayerController = class {
|
|
|
1244
2177
|
this.collider = null;
|
|
1245
2178
|
}
|
|
1246
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;
|
|
1247
2186
|
controllerInstance = null;
|
|
1248
2187
|
}
|
|
1249
2188
|
};
|
|
@@ -1252,13 +2191,16 @@ function playerController() {
|
|
|
1252
2191
|
const c = controllerInstance;
|
|
1253
2192
|
return {
|
|
1254
2193
|
init: (opts, callback) => c.init(opts, callback),
|
|
2194
|
+
loadVehicleModel: (params) => c.loadVehicleModel(params),
|
|
1255
2195
|
changeView: () => c.changeView(),
|
|
1256
2196
|
reset: (pos) => c.reset(pos),
|
|
1257
2197
|
update: (dt) => c.update(dt),
|
|
1258
2198
|
destroy: () => c.destroy(),
|
|
1259
2199
|
setInput: (i) => c.setInput(i),
|
|
1260
2200
|
getposition: () => c.getPosition(),
|
|
1261
|
-
|
|
2201
|
+
getPerson: () => c.person,
|
|
2202
|
+
getActiveVehicle: () => c.activeVehicle,
|
|
2203
|
+
getAllVehicles: () => c.vehicles
|
|
1262
2204
|
};
|
|
1263
2205
|
}
|
|
1264
2206
|
function onAllEvent() {
|