three-player-controller 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,894 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.ts
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ offAllEvent: () => offAllEvent,
33
+ onAllEvent: () => onAllEvent,
34
+ usePlayer: () => usePlayer
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/playerController.ts
39
+ var THREE = __toESM(require("three"));
40
+ var import_three_mesh_bvh = require("three-mesh-bvh");
41
+ var import_RoundedBoxGeometry = require("three/examples/jsm/geometries/RoundedBoxGeometry.js");
42
+ var import_DRACOLoader = require("three/examples/jsm/loaders/DRACOLoader.js");
43
+ var import_GLTFLoader = require("three/examples/jsm/loaders/GLTFLoader.js");
44
+ var BufferGeometryUtils = __toESM(require("three/examples/jsm/utils/BufferGeometryUtils.js"));
45
+ var controllerInstance = null;
46
+ var PlayerController = class {
47
+ // 射线检测时只返回第一个碰撞
48
+ constructor() {
49
+ this.loader = new import_GLTFLoader.GLTFLoader();
50
+ this.isFirstPerson = false;
51
+ this.mouseSensity = 5;
52
+ this.boundingBoxMinY = 0;
53
+ // 测试参数
54
+ this.displayPlayer = false;
55
+ this.displayCollider = false;
56
+ this.displayVisualizer = false;
57
+ // 场景对象
58
+ this.collider = null;
59
+ this.visualizer = null;
60
+ this.person = null;
61
+ // 状态开关
62
+ this.playerIsOnGround = false;
63
+ this.isUpdatePlayer = true;
64
+ // 输入状态
65
+ this.fwdPressed = false;
66
+ // w
67
+ this.bkdPressed = false;
68
+ // s
69
+ this.lftPressed = false;
70
+ // a
71
+ this.rgtPressed = false;
72
+ // d
73
+ this.spacePressed = false;
74
+ // 空格
75
+ this.ctPressed = false;
76
+ // ctrl
77
+ this.shiftPressed = false;
78
+ // shift
79
+ this.sustainSpacePressed = false;
80
+ // 空格键是否持续按下
81
+ this.spaceLongPressTimer = null;
82
+ // 第三人称
83
+ this._camCollisionLerp = 0.18;
84
+ // 平滑系数
85
+ this._camEpsilon = 0.35;
86
+ // 摄像机与障碍物之间的安全距离(米)
87
+ this._minCamDistance = 1;
88
+ // 摄像机最小距离
89
+ this._maxCamDistance = 4.4;
90
+ // 摄像机最大距离
91
+ // 物理/运动
92
+ this.playerVelocity = new THREE.Vector3();
93
+ // 玩家速度向量
94
+ this.upVector = new THREE.Vector3(0, 1, 0);
95
+ // 临时复用向量/矩阵
96
+ this.tempVector = new THREE.Vector3();
97
+ this.tempVector2 = new THREE.Vector3();
98
+ this.tempBox = new THREE.Box3();
99
+ this.tempMat = new THREE.Matrix4();
100
+ this.tempSegment = new THREE.Line3();
101
+ // 复用向量:用于相机朝向 / 移动
102
+ this.camDir = new THREE.Vector3();
103
+ this.moveDir = new THREE.Vector3();
104
+ this.targetQuat = new THREE.Quaternion();
105
+ this.targetMat = new THREE.Matrix4();
106
+ this.rotationSpeed = 10;
107
+ this.DIR_FWD = new THREE.Vector3(0, 0, -1);
108
+ this.DIR_BKD = new THREE.Vector3(0, 0, 1);
109
+ this.DIR_LFT = new THREE.Vector3(-1, 0, 0);
110
+ this.DIR_RGT = new THREE.Vector3(1, 0, 0);
111
+ this._personToCam = new THREE.Vector3();
112
+ this._originTmp = new THREE.Vector3();
113
+ this._raycaster = new THREE.Raycaster(
114
+ new THREE.Vector3(),
115
+ new THREE.Vector3(0, -1, 0)
116
+ );
117
+ this._raycasterPersonToCam = new THREE.Raycaster(
118
+ new THREE.Vector3(),
119
+ new THREE.Vector3()
120
+ );
121
+ // 键盘按下事件
122
+ this._boundOnKeydown = async (e) => {
123
+ if (e.ctrlKey && (e.code === "KeyW" || e.code === "KeyA" || e.code === "KeyS" || e.code === "KeyD")) {
124
+ e.preventDefault();
125
+ }
126
+ switch (e.code) {
127
+ case "KeyW":
128
+ this.fwdPressed = true;
129
+ this.setAnimationByPressed();
130
+ break;
131
+ case "KeyS":
132
+ this.bkdPressed = true;
133
+ this.setAnimationByPressed();
134
+ break;
135
+ case "KeyD":
136
+ this.rgtPressed = true;
137
+ this.setAnimationByPressed();
138
+ break;
139
+ case "KeyA":
140
+ this.lftPressed = true;
141
+ this.setAnimationByPressed();
142
+ break;
143
+ case "ShiftLeft":
144
+ this.shiftPressed = true;
145
+ this.setAnimationByPressed();
146
+ break;
147
+ case "Space":
148
+ if (!this.spacePressed) this.spacePressed = true;
149
+ if (!this.spaceLongPressTimer) {
150
+ this.spaceLongPressTimer = setTimeout(() => {
151
+ this.sustainSpacePressed = true;
152
+ }, 2e3);
153
+ }
154
+ break;
155
+ case "ControlLeft":
156
+ this.ctPressed = true;
157
+ break;
158
+ case "KeyV":
159
+ this.changeView();
160
+ break;
161
+ }
162
+ };
163
+ // 键盘抬起事件
164
+ this._boundOnKeyup = (e) => {
165
+ switch (e.code) {
166
+ case "KeyW":
167
+ this.fwdPressed = false;
168
+ this.setAnimationByPressed();
169
+ break;
170
+ case "KeyS":
171
+ this.bkdPressed = false;
172
+ this.setAnimationByPressed();
173
+ break;
174
+ case "KeyD":
175
+ this.rgtPressed = false;
176
+ this.setAnimationByPressed();
177
+ break;
178
+ case "KeyA":
179
+ this.lftPressed = false;
180
+ this.setAnimationByPressed();
181
+ break;
182
+ case "ShiftLeft":
183
+ this.shiftPressed = false;
184
+ this.setAnimationByPressed();
185
+ break;
186
+ case "Space":
187
+ if (this.spaceLongPressTimer) {
188
+ clearTimeout(this.spaceLongPressTimer);
189
+ this.spaceLongPressTimer = null;
190
+ }
191
+ this.spacePressed = false;
192
+ this.sustainSpacePressed = false;
193
+ break;
194
+ case "ControlLeft":
195
+ this.ctPressed = false;
196
+ break;
197
+ }
198
+ };
199
+ // 根据按键设置人物动画
200
+ this.setAnimationByPressed = () => {
201
+ if (this.playerIsOnGround) {
202
+ if (!this.fwdPressed && !this.bkdPressed && !this.lftPressed && !this.rgtPressed) {
203
+ this.playPersonAnimationByName("idle");
204
+ return;
205
+ }
206
+ if (this.fwdPressed) {
207
+ if (this.shiftPressed) {
208
+ this.playPersonAnimationByName("running");
209
+ } else {
210
+ this.playPersonAnimationByName("walking");
211
+ }
212
+ return;
213
+ }
214
+ if (!this.isFirstPerson && (this.lftPressed || this.rgtPressed || this.bkdPressed)) {
215
+ if (this.shiftPressed) {
216
+ this.playPersonAnimationByName("running");
217
+ } else {
218
+ this.playPersonAnimationByName("walking");
219
+ }
220
+ return;
221
+ }
222
+ if (this.lftPressed) {
223
+ this.playPersonAnimationByName("left_walking");
224
+ return;
225
+ }
226
+ if (this.rgtPressed) {
227
+ this.playPersonAnimationByName("right_walking");
228
+ return;
229
+ }
230
+ if (this.bkdPressed) {
231
+ this.playPersonAnimationByName("walking_backward");
232
+ return;
233
+ }
234
+ }
235
+ };
236
+ // 鼠标移动事件
237
+ this._mouseMove = (e) => {
238
+ if (document.pointerLockElement !== document.body) return;
239
+ if (this.isFirstPerson) {
240
+ const yaw = -e.movementX * 1e-4 * this.mouseSensity;
241
+ const pitch = -e.movementY * 1e-4 * this.mouseSensity;
242
+ this.player.rotateY(yaw);
243
+ this.camera.rotation.x = THREE.MathUtils.clamp(
244
+ this.camera.rotation.x + pitch,
245
+ -1.3,
246
+ 1.4
247
+ );
248
+ } else {
249
+ const sensitivity = 1e-4 * this.mouseSensity;
250
+ const deltaX = -e.movementX * sensitivity;
251
+ const deltaY = -e.movementY * sensitivity;
252
+ const target = this.player.position.clone();
253
+ const distance = this.camera.position.distanceTo(target);
254
+ const currentPosition = this.camera.position.clone().sub(target);
255
+ let theta = Math.atan2(currentPosition.x, currentPosition.z);
256
+ let phi = Math.acos(currentPosition.y / distance);
257
+ theta += deltaX;
258
+ phi += deltaY;
259
+ phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
260
+ const newX = distance * Math.sin(phi) * Math.sin(theta);
261
+ const newY = distance * Math.cos(phi);
262
+ const newZ = distance * Math.sin(phi) * Math.cos(theta);
263
+ this.camera.position.set(
264
+ target.x + newX,
265
+ target.y + newY,
266
+ target.z + newZ
267
+ );
268
+ this.camera.lookAt(target);
269
+ }
270
+ };
271
+ this._mouseClick = (e) => {
272
+ if (document.pointerLockElement !== document.body)
273
+ document.body.requestPointerLock();
274
+ };
275
+ this._raycaster.firstHitOnly = true;
276
+ this._raycasterPersonToCam.firstHitOnly = true;
277
+ }
278
+ // 初始化
279
+ async init(opts, callback) {
280
+ this.scene = opts.scene;
281
+ this.camera = opts.camera;
282
+ this.controls = opts.controls;
283
+ this.playerModel = opts.playerModel;
284
+ this.playerModel.scale = opts.playerModel.scale ? opts.playerModel.scale : 1;
285
+ this.initPos = opts.initPos ? opts.initPos : new THREE.Vector3(0, 0, 0);
286
+ this.visualizeDepth = 0 * this.playerModel.scale;
287
+ this.gravity = -2400 * this.playerModel.scale;
288
+ this.jumpHeight = 300 * this.playerModel.scale;
289
+ this.highJumpHeight = 1e3 * this.playerModel.scale;
290
+ this.playerSpeed = 400 * this.playerModel.scale;
291
+ await this.createBVH();
292
+ this.createPlayer();
293
+ await this.loadPersonGLB();
294
+ if (this.isFirstPerson && this.player) {
295
+ this.player.add(this.camera);
296
+ }
297
+ this.onAllEvent();
298
+ this.setCameraPos();
299
+ this.setControls();
300
+ if (callback) callback();
301
+ }
302
+ // 第一/三视角切换
303
+ changeView() {
304
+ this.isFirstPerson = !this.isFirstPerson;
305
+ if (this.isFirstPerson) {
306
+ this.player.attach(this.camera);
307
+ this.camera.position.set(
308
+ 0,
309
+ 40 * this.playerModel.scale,
310
+ 30 * this.playerModel.scale
311
+ );
312
+ this.camera.rotation.set(0, Math.PI, 0);
313
+ document.body.requestPointerLock();
314
+ } else {
315
+ this.scene.attach(this.camera);
316
+ const worldPos = this.player.position.clone();
317
+ const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(
318
+ this.player.quaternion
319
+ );
320
+ const angle = Math.atan2(dir.z, dir.x);
321
+ const offset = new THREE.Vector3(
322
+ Math.cos(angle) * 400 * this.playerModel.scale,
323
+ 200 * this.playerModel.scale,
324
+ Math.sin(angle) * 400 * this.playerModel.scale
325
+ );
326
+ this.camera.position.copy(worldPos).add(offset);
327
+ this.controls.target.copy(worldPos);
328
+ document.body.requestPointerLock();
329
+ }
330
+ }
331
+ // 摄像机/控制器设置
332
+ setCameraPos() {
333
+ if (this.isFirstPerson) {
334
+ this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
335
+ } else {
336
+ const worldPos = this.player.position.clone();
337
+ const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(
338
+ this.player.quaternion
339
+ );
340
+ const angle = Math.atan2(dir.z, dir.x);
341
+ const offset = new THREE.Vector3(
342
+ Math.cos(angle) * 400 * this.playerModel.scale,
343
+ 200 * this.playerModel.scale,
344
+ Math.sin(angle) * 400 * this.playerModel.scale
345
+ );
346
+ this.camera.position.copy(worldPos).add(offset);
347
+ }
348
+ this.camera.updateProjectionMatrix();
349
+ }
350
+ // 设置控制器
351
+ setControls() {
352
+ this.controls.enabled = false;
353
+ this.controls.maxPolarAngle = Math.PI * (230 / 360);
354
+ }
355
+ // 重置控制器
356
+ resetControls() {
357
+ this.controls.enabled = true;
358
+ this.controls.enablePan = true;
359
+ this.controls.maxPolarAngle = Math.PI / 2;
360
+ this.controls.rotateSpeed = 1;
361
+ this.controls.enableZoom = true;
362
+ this.controls.mouseButtons = {
363
+ LEFT: 0,
364
+ MIDDLE: 1,
365
+ RIGHT: 2
366
+ };
367
+ }
368
+ // 初始化加载器
369
+ async initLoader() {
370
+ const dracoLoader = new import_DRACOLoader.DRACOLoader();
371
+ dracoLoader.setDecoderPath(
372
+ "https://unpkg.com/three@0.180.0/examples/jsm/libs/draco/gltf/"
373
+ );
374
+ dracoLoader.setDecoderConfig({ type: "js" });
375
+ this.loader.setDRACOLoader(dracoLoader);
376
+ }
377
+ // 人物与动画加载
378
+ async loadPersonGLB() {
379
+ try {
380
+ const gltf = await this.loader.loadAsync(
381
+ this.playerModel.url,
382
+ (xhr) => {
383
+ }
384
+ );
385
+ this.person = gltf.scene;
386
+ this.person.name = "\u89D2\u8272";
387
+ this.person.scale.set(
388
+ 0.9 * this.playerModel.scale,
389
+ 0.9 * this.playerModel.scale,
390
+ 0.9 * this.playerModel.scale
391
+ );
392
+ this.person.position.set(0, -125 * this.playerModel.scale, 0);
393
+ this.player.add(this.person);
394
+ this.reset();
395
+ this.personMixer = new THREE.AnimationMixer(this.person);
396
+ const animations = gltf.animations ?? [];
397
+ this.personActions = /* @__PURE__ */ new Map();
398
+ const findClip = (name) => animations.find((a) => a.name === name);
399
+ const regs = [
400
+ [this.playerModel.idleAnim, "idle"],
401
+ [this.playerModel.walkAnim, "walking"],
402
+ [this.playerModel.leftWalkAnim || this.playerModel.walkAnim, "left_walking"],
403
+ [this.playerModel.rightWalkAnim || this.playerModel.walkAnim, "right_walking"],
404
+ [this.playerModel.backwardAnim || this.playerModel.walkAnim, "walking_backward"],
405
+ [this.playerModel.jumpAnim, "jumping"],
406
+ [this.playerModel.runAnim, "running"]
407
+ ];
408
+ for (const [key, clipName] of regs) {
409
+ const clip = findClip(key);
410
+ if (!clip) continue;
411
+ const action = this.personMixer.clipAction(clip);
412
+ if (clipName === "jumping") {
413
+ action.setLoop(THREE.LoopOnce, 1);
414
+ action.clampWhenFinished = true;
415
+ action.setEffectiveTimeScale(1.2);
416
+ } else {
417
+ action.setLoop(THREE.LoopRepeat, Infinity);
418
+ action.clampWhenFinished = false;
419
+ action.setEffectiveTimeScale(1);
420
+ }
421
+ action.enabled = true;
422
+ action.setEffectiveWeight(0);
423
+ this.personActions.set(clipName, action);
424
+ }
425
+ this.idleAction = this.personActions.get("idle");
426
+ this.walkAction = this.personActions.get("walking");
427
+ this.leftWalkAction = this.personActions.get("left_walking");
428
+ this.rightWalkAction = this.personActions.get("right_walking");
429
+ this.backwardAction = this.personActions.get("walking_backward");
430
+ this.jumpAction = this.personActions.get("jumping");
431
+ this.runAction = this.personActions.get("running");
432
+ this.idleAction.setEffectiveWeight(1);
433
+ this.idleAction.play();
434
+ this.actionState = this.idleAction;
435
+ this.personMixer.addEventListener("finished", (ev) => {
436
+ const finishedAction = ev.action;
437
+ if (finishedAction === this.jumpAction) {
438
+ if (this.fwdPressed) {
439
+ if (this.shiftPressed) this.playPersonAnimationByName("running");
440
+ else this.playPersonAnimationByName("walking");
441
+ return;
442
+ }
443
+ if (this.bkdPressed) {
444
+ this.playPersonAnimationByName("walking_backward");
445
+ return;
446
+ }
447
+ if (this.rgtPressed || this.lftPressed) {
448
+ this.playPersonAnimationByName("walking");
449
+ return;
450
+ }
451
+ this.playPersonAnimationByName("idle");
452
+ }
453
+ });
454
+ } catch (error) {
455
+ }
456
+ }
457
+ // 平滑切换人物动画
458
+ playPersonAnimationByName(name, fade = 0.18) {
459
+ if (!this.personActions) return;
460
+ if (this.ctPressed) return;
461
+ const next = this.personActions.get(name);
462
+ if (!next) return;
463
+ if (this.actionState === next) return;
464
+ const prev = this.actionState;
465
+ next.reset();
466
+ next.setEffectiveWeight(1);
467
+ next.play();
468
+ if (prev && prev !== next) {
469
+ prev.fadeOut(fade);
470
+ next.fadeIn(fade);
471
+ } else {
472
+ next.fadeIn(fade);
473
+ }
474
+ this.actionState = next;
475
+ }
476
+ // 创建玩家胶囊体
477
+ createPlayer() {
478
+ const material = new THREE.MeshStandardMaterial({
479
+ color: new THREE.Color(1, 0, 0),
480
+ shadowSide: THREE.DoubleSide,
481
+ depthTest: false
482
+ });
483
+ material.transparent = true;
484
+ material.opacity = this.displayPlayer ? 0.5 : 0;
485
+ material.wireframe = true;
486
+ this.player = new THREE.Mesh(
487
+ new import_RoundedBoxGeometry.RoundedBoxGeometry(
488
+ 75 * this.playerModel.scale,
489
+ 180 * this.playerModel.scale,
490
+ 75 * this.playerModel.scale,
491
+ 100 * this.playerModel.scale,
492
+ 75 * this.playerModel.scale
493
+ ),
494
+ material
495
+ );
496
+ this.player.geometry.translate(0, -30 * this.playerModel.scale, 0);
497
+ this.player.capsuleInfo = {
498
+ radius: 25 * this.playerModel.scale,
499
+ segment: new THREE.Line3(
500
+ new THREE.Vector3(),
501
+ new THREE.Vector3(0, -1, 0)
502
+ )
503
+ };
504
+ this.player.name = "\u89D2\u8272\u80F6\u56CA\u4F53";
505
+ this.player.rotateY(Math.PI / 2);
506
+ this.scene.add(this.player);
507
+ this.reset();
508
+ }
509
+ // 每帧更新
510
+ async updatePlayer(delta) {
511
+ if (!this.isUpdatePlayer || !this.player) return;
512
+ delta = Math.min(delta, 1 / 30);
513
+ this.updateMixers(delta);
514
+ if (!this.collider) return;
515
+ this.camera.getWorldDirection(this.camDir);
516
+ let angle = Math.atan2(this.camDir.z, this.camDir.x) + Math.PI / 2;
517
+ angle = 2 * Math.PI - angle;
518
+ this.moveDir.set(0, 0, 0);
519
+ if (this.fwdPressed) this.moveDir.add(this.DIR_FWD);
520
+ if (this.bkdPressed) this.moveDir.add(this.DIR_BKD);
521
+ if (this.lftPressed) this.moveDir.add(this.DIR_LFT);
522
+ if (this.rgtPressed) this.moveDir.add(this.DIR_RGT);
523
+ if (this.spacePressed && this.playerIsOnGround) {
524
+ this.playPersonAnimationByName("jumping");
525
+ setTimeout(() => {
526
+ this.playerVelocity.y = this.jumpHeight;
527
+ this.playerIsOnGround = false;
528
+ this.spacePressed = false;
529
+ this.player.position.addScaledVector(this.playerVelocity, delta);
530
+ this.player.updateMatrixWorld();
531
+ }, 200);
532
+ }
533
+ if (this.sustainSpacePressed && this.playerIsOnGround) {
534
+ this.playPersonAnimationByName("jumping");
535
+ setTimeout(() => {
536
+ this.playerVelocity.y = this.highJumpHeight;
537
+ this.playerIsOnGround = false;
538
+ this.spacePressed = false;
539
+ this.player.position.addScaledVector(this.playerVelocity, delta);
540
+ this.player.updateMatrixWorld();
541
+ }, 200);
542
+ }
543
+ this.playerSpeed = this.shiftPressed ? 900 * this.playerModel.scale : 400 * this.playerModel.scale;
544
+ if (this.moveDir.lengthSq() > 1e-6) {
545
+ this.moveDir.normalize().applyAxisAngle(this.upVector, angle);
546
+ this.player.position.addScaledVector(
547
+ this.moveDir,
548
+ this.playerSpeed * delta
549
+ );
550
+ }
551
+ let playerDistanceFromGround = Infinity;
552
+ this._originTmp.set(
553
+ this.player.position.x,
554
+ this.player.position.y,
555
+ this.player.position.z
556
+ );
557
+ this._raycaster.ray.origin.copy(this._originTmp);
558
+ const intersects = this._raycaster.intersectObject(
559
+ this.collider,
560
+ false
561
+ );
562
+ if (intersects.length > 0) {
563
+ playerDistanceFromGround = this.player.position.y - intersects[0].point.y;
564
+ }
565
+ if (playerDistanceFromGround > 130 * this.playerModel.scale) {
566
+ this.playerVelocity.y += delta * this.gravity;
567
+ this.player.position.addScaledVector(this.playerVelocity, delta);
568
+ } else {
569
+ this.playerIsOnGround = true;
570
+ }
571
+ this.player.updateMatrixWorld();
572
+ const capsuleInfo = this.player.capsuleInfo;
573
+ this.tempBox.makeEmpty();
574
+ this.tempMat.copy(this.collider.matrixWorld).invert();
575
+ this.tempSegment.copy(capsuleInfo.segment);
576
+ this.tempSegment.start.applyMatrix4(this.player.matrixWorld).applyMatrix4(this.tempMat);
577
+ this.tempSegment.end.applyMatrix4(this.player.matrixWorld).applyMatrix4(this.tempMat);
578
+ this.tempBox.expandByPoint(this.tempSegment.start);
579
+ this.tempBox.expandByPoint(this.tempSegment.end);
580
+ this.tempBox.expandByScalar(capsuleInfo.radius);
581
+ const bvh = this.collider?.geometry;
582
+ bvh?.boundsTree?.shapecast({
583
+ // 检测包围盒碰撞
584
+ intersectsBounds: (box) => box.intersectsBox(this.tempBox),
585
+ // 检测三角形碰撞
586
+ intersectsTriangle: (tri) => {
587
+ const triPoint = this.tempVector;
588
+ const capsulePoint = this.tempVector2;
589
+ const distance = tri.closestPointToSegment(
590
+ this.tempSegment,
591
+ triPoint,
592
+ capsulePoint
593
+ );
594
+ if (distance < capsuleInfo.radius) {
595
+ const depth = capsuleInfo.radius - distance;
596
+ const direction = capsulePoint.sub(triPoint).normalize();
597
+ this.tempSegment.start.addScaledVector(direction, depth);
598
+ this.tempSegment.end.addScaledVector(direction, depth);
599
+ }
600
+ }
601
+ });
602
+ const newPosition = this.tempVector.copy(this.tempSegment.start).applyMatrix4(this.collider.matrixWorld);
603
+ const deltaVector = this.tempVector2.subVectors(
604
+ newPosition,
605
+ this.player.position
606
+ );
607
+ const len = deltaVector.length();
608
+ const offset = Math.max(0, len - 1e-5);
609
+ if (offset > 0 && len > 0) {
610
+ const n = deltaVector.multiplyScalar(1 / len);
611
+ this.player.position.addScaledVector(n, offset);
612
+ this.playerVelocity.set(0, 0, 0);
613
+ }
614
+ if (!this.isFirstPerson && this.moveDir.lengthSq() > 0) {
615
+ this.camDir.y = 0;
616
+ this.camDir.normalize();
617
+ this.camDir.negate();
618
+ this.moveDir.normalize();
619
+ this.moveDir.negate();
620
+ const lookTarget = this.player.position.clone().add(this.moveDir);
621
+ this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
622
+ this.targetQuat.setFromRotationMatrix(this.targetMat);
623
+ const alpha = Math.min(1, this.rotationSpeed * delta);
624
+ this.player.quaternion.slerp(this.targetQuat, alpha);
625
+ }
626
+ if (!this.isFirstPerson) {
627
+ const lookTarget = this.player.position.clone();
628
+ lookTarget.y += 30 * this.playerModel.scale;
629
+ this.camera.position.sub(this.controls.target);
630
+ this.controls.target.copy(lookTarget);
631
+ this.camera.position.add(lookTarget);
632
+ this.controls.update();
633
+ this._personToCam.subVectors(this.camera.position, this.player.position);
634
+ const origin = this.player.position.clone().add(new THREE.Vector3(0, 0, 0));
635
+ const direction = this._personToCam.clone().normalize();
636
+ const desiredDist = this._personToCam.length();
637
+ this._raycasterPersonToCam.set(origin, direction);
638
+ this._raycasterPersonToCam.far = desiredDist;
639
+ const intersects2 = this._raycasterPersonToCam.intersectObject(
640
+ this.collider,
641
+ false
642
+ );
643
+ if (intersects2.length > 0) {
644
+ const hit = intersects2[0];
645
+ const safeDist = Math.max(
646
+ hit.distance - this._camEpsilon,
647
+ this._minCamDistance
648
+ );
649
+ const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
650
+ this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
651
+ } else {
652
+ const dis = this.player.position.distanceTo(this.camera.position);
653
+ this._raycasterPersonToCam.far = this._maxCamDistance;
654
+ const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(
655
+ this.collider,
656
+ false
657
+ );
658
+ if (dis < this._maxCamDistance) {
659
+ let safeDist = this._maxCamDistance;
660
+ if (intersectsMaxDis.length) {
661
+ const hitMax = intersectsMaxDis[0];
662
+ safeDist = hitMax.distance - this._camEpsilon;
663
+ }
664
+ const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
665
+ this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
666
+ }
667
+ }
668
+ }
669
+ if (this.player.position.y < this.boundingBoxMinY - 1) {
670
+ this._originTmp.set(
671
+ this.player.position.x,
672
+ 1e4,
673
+ this.player.position.z
674
+ );
675
+ this._raycaster.ray.origin.copy(this._originTmp);
676
+ const intersects2 = this._raycaster.intersectObject(
677
+ this.collider,
678
+ false
679
+ );
680
+ if (intersects2.length > 0) {
681
+ console.log("\u73A9\u5BB6\u4E3Abug\u610F\u5916\u6389\u843D");
682
+ this.reset(
683
+ new THREE.Vector3(
684
+ this.player.position.x,
685
+ intersects2[0].point.y + 5,
686
+ this.player.position.z
687
+ )
688
+ );
689
+ } else {
690
+ console.log("\u73A9\u5BB6\u6B63\u5E38\u6389\u843D");
691
+ this.reset(
692
+ new THREE.Vector3(
693
+ this.player.position.x,
694
+ this.player.position.y + 15,
695
+ this.player.position.z
696
+ )
697
+ );
698
+ }
699
+ }
700
+ }
701
+ // 重置 / 销毁
702
+ reset(position) {
703
+ if (!this.player) return;
704
+ this.playerVelocity.set(0, 0, 0);
705
+ this.player.position.copy(position ? position : this.initPos);
706
+ }
707
+ // 销毁
708
+ destroy() {
709
+ this.offAllEvent();
710
+ if (this.player) {
711
+ this.player.remove(this.camera);
712
+ this.scene.remove(this.player);
713
+ }
714
+ this.player = null;
715
+ if (this.person) {
716
+ this.scene.remove(this.person);
717
+ this.person = null;
718
+ }
719
+ this.resetControls();
720
+ if (this.visualizer) {
721
+ this.scene.remove(this.visualizer);
722
+ this.visualizer = null;
723
+ }
724
+ if (this.collider) {
725
+ this.scene.remove(this.collider);
726
+ this.collider = null;
727
+ }
728
+ controllerInstance = null;
729
+ }
730
+ // 事件绑定
731
+ onAllEvent() {
732
+ this.isUpdatePlayer = true;
733
+ document.body.requestPointerLock();
734
+ window.addEventListener("keydown", this._boundOnKeydown);
735
+ window.addEventListener("keyup", this._boundOnKeyup);
736
+ window.addEventListener("mousemove", this._mouseMove);
737
+ window.addEventListener("click", this._mouseClick);
738
+ }
739
+ // 事件解绑
740
+ offAllEvent() {
741
+ this.isUpdatePlayer = false;
742
+ document.exitPointerLock();
743
+ window.removeEventListener("keydown", this._boundOnKeydown);
744
+ window.removeEventListener("keyup", this._boundOnKeyup);
745
+ window.removeEventListener("mousemove", this._mouseMove);
746
+ window.removeEventListener("click", this._mouseClick);
747
+ }
748
+ // 更新模型动画
749
+ updateMixers(delta) {
750
+ if (this.personMixer) this.personMixer.update(delta);
751
+ if (this.droneMixer) this.droneMixer.update(delta);
752
+ }
753
+ // BVH构建
754
+ async createBVH(meshUrl = "") {
755
+ await this.initLoader();
756
+ const ensureAttributesMinimal = (geom) => {
757
+ if (!geom.attributes.position) {
758
+ return null;
759
+ }
760
+ if (!geom.attributes.normal) geom.computeVertexNormals();
761
+ if (!geom.attributes.uv) {
762
+ const count = geom.attributes.position.count;
763
+ const dummyUV = new Float32Array(count * 2);
764
+ geom.setAttribute("uv", new THREE.BufferAttribute(dummyUV, 2));
765
+ }
766
+ return geom;
767
+ };
768
+ const collected = [];
769
+ if (meshUrl == "") {
770
+ if (this.collider) {
771
+ this.scene.remove(this.collider);
772
+ this.collider = null;
773
+ }
774
+ this.scene.traverse((c) => {
775
+ const mesh = c;
776
+ if (mesh?.isMesh && mesh.geometry && c.name !== "\u89D2\u8272\u80F6\u56CA\u4F53") {
777
+ try {
778
+ let geom = mesh.geometry.clone();
779
+ geom.applyMatrix4(mesh.matrixWorld);
780
+ if (geom.index) geom = geom.toNonIndexed();
781
+ const safe = ensureAttributesMinimal(geom);
782
+ if (safe) collected.push(safe);
783
+ } catch (e) {
784
+ console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
785
+ }
786
+ }
787
+ });
788
+ if (!collected.length) {
789
+ return;
790
+ }
791
+ const attrMap = /* @__PURE__ */ new Map();
792
+ const attrConflict = /* @__PURE__ */ new Set();
793
+ for (const g of collected) {
794
+ for (const name of Object.keys(g.attributes)) {
795
+ const attr = g.attributes[name];
796
+ const ctor = attr.array.constructor;
797
+ const itemSize = attr.itemSize;
798
+ if (!attrMap.has(name)) {
799
+ attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1 });
800
+ } else {
801
+ const m = attrMap.get(name);
802
+ if (m.itemSize !== itemSize || m.arrayCtor !== ctor)
803
+ attrConflict.add(name);
804
+ else m.examples++;
805
+ }
806
+ }
807
+ }
808
+ if (attrConflict.size) {
809
+ for (const g of collected) {
810
+ for (const name of Array.from(attrConflict)) {
811
+ if (g.attributes[name]) g.deleteAttribute(name);
812
+ }
813
+ }
814
+ for (const name of attrConflict) attrMap.delete(name);
815
+ }
816
+ const attrNames = Array.from(attrMap.keys());
817
+ for (const g of collected) {
818
+ const count = g.attributes.position.count;
819
+ for (const name of attrNames) {
820
+ if (!g.attributes[name]) {
821
+ const meta = attrMap.get(name);
822
+ const len = count * meta.itemSize;
823
+ const array = new meta.arrayCtor(len);
824
+ g.setAttribute(
825
+ name,
826
+ new THREE.BufferAttribute(array, meta.itemSize)
827
+ );
828
+ }
829
+ }
830
+ }
831
+ } else {
832
+ const gltf = await this.loader.loadAsync(meshUrl, (xhr) => {
833
+ });
834
+ const mesh = gltf.scene.children[0];
835
+ mesh.name = "BVH\u52A0\u8F7D\u6A21\u578B";
836
+ let geom = mesh.geometry.clone();
837
+ geom.applyMatrix4(mesh.matrixWorld);
838
+ if (geom.index) geom = geom.toNonIndexed();
839
+ const safe = ensureAttributesMinimal(geom);
840
+ if (safe) collected.push(safe);
841
+ }
842
+ const merged = BufferGeometryUtils.mergeGeometries(collected, false);
843
+ if (!merged) {
844
+ console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
845
+ return;
846
+ }
847
+ merged.boundsTree = new import_three_mesh_bvh.MeshBVH(merged);
848
+ this.collider = new THREE.Mesh(
849
+ merged,
850
+ new THREE.MeshBasicMaterial({
851
+ color: "red",
852
+ opacity: 0.2,
853
+ transparent: true,
854
+ wireframe: false
855
+ })
856
+ );
857
+ if (this.displayCollider) this.scene.add(this.collider);
858
+ if (this.displayVisualizer) {
859
+ if (this.visualizer) this.scene.remove(this.visualizer);
860
+ this.visualizer = new import_three_mesh_bvh.MeshBVHHelper(this.collider, this.visualizeDepth);
861
+ this.scene.add(this.visualizer);
862
+ }
863
+ this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
864
+ console.log("bvh\u52A0\u8F7D\u6A21\u578B\u6210\u529F", this.collider);
865
+ }
866
+ };
867
+ function usePlayer() {
868
+ if (!controllerInstance) controllerInstance = new PlayerController();
869
+ const c = controllerInstance;
870
+ return {
871
+ init: (opts, callback) => c.init(opts, callback),
872
+ changeView: () => c.changeView(),
873
+ createBVH: (url = "") => c.createBVH(url),
874
+ createPlayer: () => c.createPlayer(),
875
+ reset: (pos) => c.reset(pos),
876
+ updatePlayer: (dt) => c.updatePlayer(dt),
877
+ destroy: () => c.destroy()
878
+ };
879
+ }
880
+ function onAllEvent() {
881
+ if (!controllerInstance) controllerInstance = new PlayerController();
882
+ controllerInstance.onAllEvent();
883
+ }
884
+ function offAllEvent() {
885
+ if (!controllerInstance) return;
886
+ controllerInstance.offAllEvent();
887
+ }
888
+ // Annotate the CommonJS export names for ESM import in node:
889
+ 0 && (module.exports = {
890
+ offAllEvent,
891
+ onAllEvent,
892
+ usePlayer
893
+ });
894
+ //# sourceMappingURL=index.js.map