three-base 2.0.129 → 2.0.134

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.
Files changed (123) hide show
  1. package/ApngTexture.d.ts +13 -13
  2. package/ApngTexture.js +61 -61
  3. package/ApngTextureManage.d.ts +18 -18
  4. package/ApngTextureManage.js +87 -87
  5. package/DirectionalLight.d.ts +6 -0
  6. package/DirectionalLight.js +68 -0
  7. package/EventMesh.d.ts +18 -18
  8. package/EventMesh.js +59 -59
  9. package/ExtrudeMesh.d.ts +112 -112
  10. package/ExtrudeMesh.js +366 -366
  11. package/FPScontrol.d.ts +71 -50
  12. package/FPScontrol.js +505 -232
  13. package/Fire/Fire.d.ts +28 -28
  14. package/Fire/Fire.js +79 -79
  15. package/Fire/FireGromtry.d.ts +9 -9
  16. package/Fire/FireGromtry.js +40 -40
  17. package/Fire/FireMaterial.d.ts +28 -28
  18. package/Fire/FireMaterial.js +32 -32
  19. package/Fire/index.d.ts +1 -1
  20. package/Fire/index.js +1 -1
  21. package/Grass/Grass.d.ts +8 -8
  22. package/Grass/Grass.js +38 -38
  23. package/Grass/index.d.ts +1 -1
  24. package/Grass/index.js +1 -1
  25. package/Ground1/Ground1.d.ts +11 -11
  26. package/Ground1/Ground1.js +54 -54
  27. package/Ground1/index.d.ts +1 -1
  28. package/Ground1/index.js +1 -1
  29. package/InstancedMeshBase.d.ts +39 -39
  30. package/InstancedMeshBase.js +293 -293
  31. package/KeyFrameAnimation.d.ts +25 -25
  32. package/KeyFrameAnimation.js +78 -78
  33. package/MeshBase.d.ts +35 -35
  34. package/MeshBase.js +223 -223
  35. package/Mirror.d.ts +25 -25
  36. package/Mirror.js +139 -139
  37. package/PipeMesh/PathGeometry.d.ts +44 -44
  38. package/PipeMesh/PathGeometry.js +300 -300
  39. package/PipeMesh/PathPoint.d.ts +17 -17
  40. package/PipeMesh/PathPoint.js +31 -31
  41. package/PipeMesh/PathPointList.d.ts +31 -31
  42. package/PipeMesh/PathPointList.js +208 -208
  43. package/PipeMesh/PathTubeGeometry.d.ts +25 -25
  44. package/PipeMesh/PathTubeGeometry.js +130 -130
  45. package/PipeMesh/PipeMesh.d.ts +25 -25
  46. package/PipeMesh/PipeMesh.js +63 -63
  47. package/PipeMesh/index.d.ts +1 -1
  48. package/PipeMesh/index.js +1 -1
  49. package/PlaneModal1/PlaneModal1.d.ts +31 -31
  50. package/PlaneModal1/PlaneModal1.js +105 -105
  51. package/PlaneModal1/index.d.ts +1 -1
  52. package/PlaneModal1/index.js +1 -1
  53. package/PlaneText.d.ts +39 -39
  54. package/PlaneText.js +30 -30
  55. package/PlaneWithHtml.d.ts +11 -11
  56. package/PlaneWithHtml.js +63 -68
  57. package/PointLight/PointLight.d.ts +14 -13
  58. package/PointLight/PointLight.js +70 -64
  59. package/PointLight/index.d.ts +1 -1
  60. package/PointLight/index.js +1 -1
  61. package/Sky/Sky.d.ts +9 -9
  62. package/Sky/Sky.js +22 -22
  63. package/Sky/index.d.ts +1 -1
  64. package/Sky/index.js +1 -1
  65. package/SpotLight.d.ts +6 -0
  66. package/SpotLight.js +72 -0
  67. package/SpriteModal1/SpriteModal1.d.ts +30 -30
  68. package/SpriteModal1/SpriteModal1.js +110 -110
  69. package/SpriteModal1/index.d.ts +1 -1
  70. package/SpriteModal1/index.js +1 -1
  71. package/SpriteText.d.ts +45 -45
  72. package/SpriteText.js +163 -163
  73. package/SpriteText2.d.ts +47 -47
  74. package/SpriteText2.js +198 -198
  75. package/SpriteWithHtml.d.ts +7 -7
  76. package/SpriteWithHtml.js +44 -49
  77. package/Text3D/Text3D.d.ts +20 -20
  78. package/Text3D/Text3D.js +62 -62
  79. package/Text3D/fontController.d.ts +59 -59
  80. package/Text3D/fontController.js +199 -199
  81. package/Text3D/index.d.ts +1 -1
  82. package/Text3D/index.js +1 -1
  83. package/ThreeBase.d.ts +97 -81
  84. package/ThreeBase.js +613 -559
  85. package/ThreeMouse.d.ts +40 -40
  86. package/ThreeMouse.js +407 -409
  87. package/Transition.d.ts +40 -40
  88. package/Transition.js +210 -207
  89. package/Water/Water.d.ts +5 -0
  90. package/Water/Water.js +26 -0
  91. package/Water/waternormals.jpg +0 -0
  92. package/declare.d.ts +167 -0
  93. package/index.d.ts +1 -1
  94. package/index.js +2 -2
  95. package/keyContorls.d.ts +25 -25
  96. package/keyContorls.js +98 -98
  97. package/package.json +1 -1
  98. package/pass/bloom_pass.d.ts +2 -2
  99. package/pass/bloom_pass.js +99 -99
  100. package/pass/cover_pass.d.ts +2 -2
  101. package/pass/cover_pass.js +95 -95
  102. package/pass/frosted_glass_pass.d.ts +2 -0
  103. package/pass/frosted_glass_pass.js +275 -0
  104. package/pass/fsQuad.d.ts +9 -9
  105. package/pass/fsQuad.js +28 -28
  106. package/pass/glow_pass.d.ts +2 -2
  107. package/pass/glow_pass.js +310 -254
  108. package/pass/ssao_pass.d.ts +11 -0
  109. package/pass/ssao_pass.js +294 -0
  110. package/pass/stroke_pass.d.ts +2 -2
  111. package/pass/stroke_pass.js +199 -199
  112. package/shaders/FXAAShaderMaterial.d.ts +3 -3
  113. package/shaders/FXAAShaderMaterial.js +21 -21
  114. package/shaders/baseTextureToRenderShader.d.ts +2 -2
  115. package/shaders/baseTextureToRenderShader.js +14 -14
  116. package/shaders/copyShader.d.ts +3 -3
  117. package/shaders/copyShader.js +14 -14
  118. package/shaders.d.ts +5 -5
  119. package/shaders.js +42 -42
  120. package/threeLoader.d.ts +34 -34
  121. package/threeLoader.js +527 -527
  122. package/threeUtils.d.ts +52 -52
  123. package/threeUtils.js +330 -330
package/FPScontrol.js CHANGED
@@ -1,232 +1,505 @@
1
- /*
2
- * @Author: xiaosihan
3
- * @Date: 2022-05-04 23:01:36
4
- * @Last Modified by: xiaosihan
5
- * @Last Modified time: 2024-11-07 21:57:39
6
- */
7
- import { Clock, EventDispatcher, Vector3, } from "three";
8
- import { Capsule } from "three/examples/jsm/math/Capsule";
9
- import { Octree } from "three/examples/jsm/math/Octree";
10
- import { clamp, degToRad } from "three/src/math/MathUtils";
11
- /**
12
- * 第一人称控制器
13
- *
14
- * @export
15
- * @class FPScontrol
16
- * @extends {EventDispatcher}
17
- */
18
- export default class FPScontrol extends EventDispatcher {
19
- canvas;
20
- constructor(camera, canvas) {
21
- super();
22
- // 相机对象
23
- this.camera = camera;
24
- this.camera.rotation.order = "YXZ";
25
- this.canvas = canvas;
26
- this.init(canvas);
27
- }
28
- // 是否启用
29
- enable = false;
30
- GRAVITY = 30; // 重力
31
- STEPS_PER_FRAME = 5; // 每帧碰撞计算的次数
32
- speed = 1; // 速度
33
- camera;
34
- // 移动时是否保持惯性 由外部来配置
35
- inertia = true;
36
- // 八叉树数组
37
- octreeMap = new Map();
38
- octree = (() => {
39
- const octree = new Octree();
40
- octree.getCapsuleTriangles = (capsule, triangles) => {
41
- const octrees = Array.from(this.octreeMap.values());
42
- for (let octree of octrees) {
43
- for (let i = 0; i < octree.subTrees.length; i++) {
44
- const subTree = octree.subTrees[i];
45
- if (!capsule.intersectsBox(subTree.box))
46
- continue;
47
- if (subTree.triangles.length > 0) {
48
- for (let j = 0; j < subTree.triangles.length; j++) {
49
- if (triangles.indexOf(subTree.triangles[j]) === -1)
50
- triangles.push(subTree.triangles[j]);
51
- }
52
- }
53
- else {
54
- subTree.getCapsuleTriangles(capsule, triangles);
55
- }
56
- }
57
- }
58
- return triangles;
59
- };
60
- return octree;
61
- })();
62
- addOctree(obj) {
63
- const octree = new Octree();
64
- octree.fromGraphNode(obj);
65
- this.octreeMap.set(obj.id, octree);
66
- return octree;
67
- }
68
- // 用户自己的碰撞体积
69
- playerCollider = new Capsule(new Vector3(0, 0.35, 0), new Vector3(0, 1, 0), 0.35);
70
- // 时钟对象
71
- clock = new Clock();
72
- prePlayerVelocity = new Vector3();
73
- playerVelocity = new Vector3();
74
- // 起跳中
75
- jumping = false;
76
- playerDirection = new Vector3();
77
- prePlayerOnFloor = false;
78
- playerOnFloor = false;
79
- mouseTime = 0;
80
- isMousedown = false;
81
- keyStates = {};
82
- init(canvas) {
83
- this.playerCollider.end.set(0, 1, 0);
84
- document.addEventListener("keydown", this.keydown);
85
- document.addEventListener("keyup", this.keyup);
86
- canvas.addEventListener("pointerdown", this.mousedown);
87
- document.addEventListener("pointerup", this.mouseup);
88
- document.body.addEventListener("pointermove", this.mousemove);
89
- this.enable = true;
90
- }
91
- keydown = (event) => {
92
- this.keyStates[event.code] = true;
93
- };
94
- keyup = (event) => {
95
- this.keyStates[event.code] = false;
96
- };
97
- mousedown = () => {
98
- this.isMousedown = true;
99
- this.mouseTime = performance.now();
100
- };
101
- mouseup = () => {
102
- this.isMousedown = false;
103
- };
104
- mousemove = (event) => {
105
- if (this.enable && this.isMousedown) {
106
- this.camera.rotation.y -= event.movementX / 500;
107
- this.camera.rotation.x -= event.movementY / 500;
108
- }
109
- };
110
- // 获取前进的向量
111
- getForwardVector = () => {
112
- this.camera.getWorldDirection(this.playerDirection);
113
- this.playerDirection.y = 0;
114
- this.playerDirection.normalize();
115
- return this.playerDirection;
116
- };
117
- // 获取 后退的向量
118
- getSideVector = () => {
119
- this.camera.getWorldDirection(this.playerDirection);
120
- this.playerDirection.y = 0;
121
- this.playerDirection.normalize();
122
- this.playerDirection.cross(this.camera.up);
123
- return this.playerDirection;
124
- };
125
- // 玩家移动控制器
126
- controls = (deltaTime) => {
127
- const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8) * this.speed;
128
- // const speedDelta = this.speed;
129
- if (this.keyStates["KeyW"]) {
130
- this.playerVelocity.add(this.getForwardVector().multiplyScalar(speedDelta));
131
- }
132
- if (this.keyStates["KeyS"]) {
133
- this.playerVelocity.add(this.getForwardVector().multiplyScalar(-speedDelta));
134
- }
135
- if (this.keyStates["KeyA"]) {
136
- this.playerVelocity.add(this.getSideVector().multiplyScalar(-speedDelta));
137
- }
138
- if (this.keyStates["KeyD"]) {
139
- this.playerVelocity.add(this.getSideVector().multiplyScalar(speedDelta));
140
- }
141
- if (this.playerOnFloor &&
142
- !this.keyStates["KeyW"] &&
143
- !this.keyStates["KeyS"] &&
144
- !this.keyStates["KeyA"] &&
145
- !this.keyStates["KeyD"]) {
146
- if (!this.inertia) {
147
- // 关闭惯性时就大幅增加动能的衰减
148
- this.playerVelocity.x *= 0.85;
149
- this.playerVelocity.z *= 0.85;
150
- }
151
- }
152
- if (this.playerOnFloor) {
153
- if (this.keyStates["Space"]) {
154
- this.playerVelocity.y = 15;
155
- this.jumping = true;
156
- this.dispatchEvent({ type: "jump_start" });
157
- }
158
- }
159
- };
160
- // 更新玩家的位置
161
- updatePlayer = (deltaTime) => {
162
- let damping = Math.exp(-4 * deltaTime) - 1;
163
- if (!this.playerOnFloor) {
164
- this.playerVelocity.y -= this.GRAVITY * deltaTime;
165
- damping *= 0.1;
166
- }
167
- this.playerVelocity.addScaledVector(this.playerVelocity, damping);
168
- const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime);
169
- this.playerCollider.translate(deltaPosition);
170
- this.playerCollisions();
171
- this.camera.position.copy(this.playerCollider.end);
172
- this.camera.rotation.x = clamp(this.camera.rotation.x, degToRad(-89), degToRad(89));
173
- };
174
- // 玩家的碰撞检测
175
- playerCollisions() {
176
- this.playerOnFloor = false;
177
- // 遍历 八叉树 做碰撞检测
178
- const result = this.octree.capsuleIntersect(this.playerCollider);
179
- if (result) {
180
- this.playerOnFloor = result.normal.y > 0;
181
- if (!this.playerOnFloor) {
182
- this.playerVelocity.addScaledVector(result.normal, -result.normal.dot(this.playerVelocity));
183
- }
184
- const { x: preX, y: preY, z: preZ } = this.prePlayerVelocity;
185
- const { x, y, z } = this.playerVelocity;
186
- if (Math.abs(preX + preZ) < 0.1 && Math.abs(x + z) > 0.1) {
187
- this.dispatchEvent({ type: "move_start" });
188
- }
189
- else if (Math.abs(preX + preZ) > 0.1 && Math.abs(x + z) < 0.1) {
190
- this.dispatchEvent({ type: "move_end" });
191
- }
192
- this.prePlayerVelocity.copy(this.playerVelocity);
193
- this.playerCollider.translate(result.normal.multiplyScalar(result.depth));
194
- }
195
- if (this.jumping && !this.prePlayerOnFloor && this.playerOnFloor) {
196
- // 分发落地事件
197
- this.jumping = false;
198
- this.dispatchEvent({ type: "jump_end" });
199
- }
200
- this.prePlayerOnFloor = this.playerOnFloor;
201
- }
202
- // 更新控制器的位置
203
- teleportPlayerIfOob = () => {
204
- if (this.camera.position.y <= -10) {
205
- this.playerCollider.start.set(0, 0.35, 0);
206
- this.playerCollider.end.set(0, 1, 10);
207
- this.playerCollider.radius = 0.35;
208
- this.camera.position.copy(this.playerCollider.end);
209
- // this.camera.position.set(0, 3, 0);
210
- }
211
- };
212
- // 更新
213
- update() {
214
- if (this.enable) {
215
- const deltaTime = Math.min(0.05, this.clock.getDelta()) / this.STEPS_PER_FRAME;
216
- for (let i = 0; i < this.STEPS_PER_FRAME; i++) {
217
- this.controls(deltaTime);
218
- this.updatePlayer(deltaTime);
219
- this.teleportPlayerIfOob();
220
- }
221
- }
222
- }
223
- // 销毁事件
224
- disposer() {
225
- this.enable = false;
226
- document.removeEventListener("keydown", this.keydown);
227
- document.removeEventListener("keyup", this.keyup);
228
- this.canvas.removeEventListener("pointerdown", this.mousedown);
229
- document.removeEventListener("pointerup", this.mouseup);
230
- document.body.removeEventListener("pointermove", this.mousemove);
231
- }
232
- }
1
+ /*
2
+ * @Author: xiaosihan
3
+ * @Date: 2022-05-04 23:01:36
4
+ * @Last Modified by: xiaosihan
5
+ * @Last Modified time: 2024-11-07 21:57:39
6
+ */
7
+ import { BufferGeometry, Clock, EventDispatcher, Float32BufferAttribute, Group, Line, LineBasicMaterial, Mesh, InstancedMesh, Raycaster, Vector3, } from "three";
8
+ import { Capsule } from "three/examples/jsm/math/Capsule";
9
+ import { clamp, degToRad } from "three/src/math/MathUtils";
10
+ import { acceleratedRaycast, computeBoundsTree } from "three-mesh-bvh";
11
+ // 确保 three-mesh-bvh 补丁已应用(含 InstancedMesh)
12
+ if (Mesh.prototype.raycast !== acceleratedRaycast) {
13
+ Mesh.prototype.raycast = acceleratedRaycast;
14
+ }
15
+ if (InstancedMesh.prototype.raycast !== acceleratedRaycast) {
16
+ InstancedMesh.prototype.raycast = acceleratedRaycast;
17
+ }
18
+ /** 第一/三人称控制器(6 射线 + 迭代推离,userData.fpsCollision 标记碰撞体) */
19
+ export default class FPScontrol extends EventDispatcher {
20
+ canvas;
21
+ /**
22
+ * @param camera 相机
23
+ * @param canvas canvas 元素
24
+ * @param scene 场景,用于射线碰撞检测(会递归检测所有子对象)
25
+ */
26
+ constructor(camera, canvas, scene) {
27
+ super();
28
+ this.camera = camera;
29
+ this.camera.rotation.order = "YXZ";
30
+ this.canvas = canvas;
31
+ this.scene = scene;
32
+ this.init(canvas);
33
+ }
34
+ // 是否启用
35
+ enable = false;
36
+ GRAVITY = 30; // 重力
37
+ STEPS_PER_FRAME = 5; // 每帧碰撞计算的次数
38
+ speed = 1; // 速度
39
+ camera;
40
+ // 移动时是否保持惯性 由外部来配置
41
+ inertia = true;
42
+ // 场景引用(用于射线检测)
43
+ scene;
44
+ // ========== dev / 第三人称 ==========
45
+ // 开发模式:打开后显示射线可视化
46
+ dev = false;
47
+ // 第三人称模式
48
+ thirdPerson = false;
49
+ // 第三人称参数:相机与第一人称位置的距离和高度偏移
50
+ thirdPersonDistance = 5;
51
+ thirdPersonHeight = 2;
52
+ // 第三人称轨道角
53
+ _thirdPersonAngleY = 0;
54
+ _thirdPersonAngleX = 0.5;
55
+ // 模式切换检测
56
+ _prevThirdPerson = false;
57
+ // ========== 射线碰撞检测 ==========
58
+ // 碰撞体积(胶囊体半径)
59
+ COLLIDE_VOLUME = 0.5;
60
+ // 推离步长(每次迭代推离的距离)
61
+ PUSH_DISTANCE = 0.03;
62
+ // 最大迭代次数
63
+ MAX_ITERATIONS = 100;
64
+ // 射线检测器
65
+ _raycaster = new Raycaster();
66
+ // 临时向量(复用避免 GC)
67
+ _tempVec = new Vector3();
68
+ _tempVec2 = new Vector3();
69
+ _tempVec3 = new Vector3();
70
+ _tempVec4 = new Vector3();
71
+ // 水平射线方向(4 个 45° 斜向)
72
+ HORIZONTAL_RAY_DIRECTIONS = [
73
+ new Vector3(-1, 0, -1).normalize(), // 右→前
74
+ new Vector3(1, 0, -1).normalize(), // 左→前
75
+ new Vector3(1, 0, 1).normalize(), // 左→后
76
+ new Vector3(-1, 0, 1).normalize(), // 右→后
77
+ ];
78
+ // 垂直射线方向
79
+ VERTICAL_DOWN = new Vector3(0, -1, 0); // 上→下
80
+ VERTICAL_UP = new Vector3(0, 1, 0); // 下→上
81
+ // userData.fpsCollision === true 的碰撞体列表
82
+ _colliders = [];
83
+ // 收集 fpsCollision 物体 + 首次构建 BVH(参照 ThreeMouse dispatchEvent)
84
+ _getColliders() {
85
+ this._colliders.length = 0;
86
+ this.scene.traverseVisible((obj) => {
87
+ if (obj.isMesh && obj.userData?.fpsCollision) {
88
+ const geo = obj.geometry;
89
+ if (geo && !geo.boundsTree && geo.attributes.position?.count > 0) {
90
+ computeBoundsTree.apply(geo);
91
+ }
92
+ this._colliders.push(obj);
93
+ }
94
+ });
95
+ }
96
+ // 玩家胶囊碰撞体
97
+ playerCollider = new Capsule(new Vector3(0, 0.35, 0), new Vector3(0, 1, 0), 0.35);
98
+ // 时钟对象
99
+ clock = new Clock();
100
+ prePlayerVelocity = new Vector3();
101
+ playerVelocity = new Vector3();
102
+ // 起跳中
103
+ jumping = false;
104
+ playerDirection = new Vector3();
105
+ prePlayerOnFloor = false;
106
+ playerOnFloor = false;
107
+ mouseTime = 0;
108
+ isMousedown = false;
109
+ keyStates = {};
110
+ // ========== 射线可视化 ==========
111
+ _debugRayGroup = null;
112
+ // 6 条调试线段:0-3 水平射线,4 向下射线,5 向上射线
113
+ _debugLines = [];
114
+ // 懒初始化调试射线组
115
+ _initDebugLines() {
116
+ if (this._debugRayGroup)
117
+ return;
118
+ this._debugRayGroup = new Group();
119
+ this._debugRayGroup.name = "FPSControl_DebugRays";
120
+ for (let i = 0; i < 6; i++) {
121
+ const geo = new BufferGeometry();
122
+ geo.setAttribute("position", new Float32BufferAttribute(new Float32Array(6), 3));
123
+ geo.setAttribute("color", new Float32BufferAttribute(new Float32Array(6), 3));
124
+ const mat = new LineBasicMaterial({
125
+ vertexColors: true,
126
+ linewidth: 1,
127
+ });
128
+ const line = new Line(geo, mat);
129
+ // 让调试线段不被射线检测命中
130
+ line.raycast = () => { };
131
+ line.frustumCulled = false;
132
+ this._debugLines.push(line);
133
+ this._debugRayGroup.add(line);
134
+ }
135
+ this.scene.add(this._debugRayGroup);
136
+ }
137
+ // 更新调试线段位置 + 颜色
138
+ _updateDebugLine(index, origin, farPoint, hit) {
139
+ const line = this._debugLines[index];
140
+ const pos = line.geometry.attributes.position.array;
141
+ const col = line.geometry.attributes.color.array;
142
+ // 位置:起点 → 终点
143
+ pos[0] = origin.x;
144
+ pos[1] = origin.y;
145
+ pos[2] = origin.z;
146
+ pos[3] = farPoint.x;
147
+ pos[4] = farPoint.y;
148
+ pos[5] = farPoint.z;
149
+ // 颜色渐变:起点(近端)绿色/红色 → 终点白色
150
+ const r = hit ? 1.0 : 0.0;
151
+ const g = hit ? 0.0 : 1.0;
152
+ col[0] = r;
153
+ col[1] = g;
154
+ col[2] = 0.0; // 起点
155
+ col[3] = 1.0;
156
+ col[4] = 1.0;
157
+ col[5] = 1.0; // 终点白色
158
+ line.geometry.attributes.position.needsUpdate = true;
159
+ line.geometry.attributes.color.needsUpdate = true;
160
+ }
161
+ // 销毁调试射线
162
+ _disposeDebugLines() {
163
+ if (!this._debugRayGroup)
164
+ return;
165
+ for (const line of this._debugLines) {
166
+ line.geometry.dispose();
167
+ line.material.dispose();
168
+ }
169
+ this._debugLines = [];
170
+ this.scene.remove(this._debugRayGroup);
171
+ this._debugRayGroup = null;
172
+ }
173
+ init(canvas) {
174
+ this.playerCollider.end.set(0, 1, 0);
175
+ document.addEventListener("keydown", this.keydown);
176
+ document.addEventListener("keyup", this.keyup);
177
+ canvas.addEventListener("pointerdown", this.mousedown);
178
+ document.addEventListener("pointerup", this.mouseup);
179
+ document.body.addEventListener("pointermove", this.mousemove);
180
+ this.enable = true;
181
+ }
182
+ keydown = (event) => {
183
+ this.keyStates[event.code] = true;
184
+ };
185
+ keyup = (event) => {
186
+ this.keyStates[event.code] = false;
187
+ };
188
+ mousedown = () => {
189
+ this.isMousedown = true;
190
+ this.mouseTime = performance.now();
191
+ };
192
+ mouseup = () => {
193
+ this.isMousedown = false;
194
+ };
195
+ mousemove = (event) => {
196
+ if (this.enable && this.isMousedown) {
197
+ if (this.thirdPerson) {
198
+ // 第三人称:旋转轨道角
199
+ this._thirdPersonAngleY -= event.movementX / 500;
200
+ this._thirdPersonAngleX -= event.movementY / 500;
201
+ this._thirdPersonAngleX = clamp(this._thirdPersonAngleX, degToRad(-89), degToRad(89));
202
+ }
203
+ else {
204
+ // 第一人称:直接旋转相机
205
+ this.camera.rotation.y -= event.movementX / 500;
206
+ this.camera.rotation.x -= event.movementY / 500;
207
+ }
208
+ }
209
+ };
210
+ // 获取前进的向量
211
+ getForwardVector = () => {
212
+ if (this.thirdPerson) {
213
+ // 第三人称:前进 = 轨道偏航方向
214
+ this.playerDirection.set(Math.sin(this._thirdPersonAngleY), 0, Math.cos(this._thirdPersonAngleY));
215
+ return this.playerDirection;
216
+ }
217
+ this.camera.getWorldDirection(this.playerDirection);
218
+ this.playerDirection.y = 0;
219
+ this.playerDirection.normalize();
220
+ return this.playerDirection;
221
+ };
222
+ // 获取侧向向量
223
+ getSideVector = () => {
224
+ if (this.thirdPerson) {
225
+ // 第三人称:侧向 = 轨道偏航 ±90°
226
+ this.playerDirection.set(Math.sin(this._thirdPersonAngleY + Math.PI / 2), 0, Math.cos(this._thirdPersonAngleY + Math.PI / 2));
227
+ return this.playerDirection;
228
+ }
229
+ this.camera.getWorldDirection(this.playerDirection);
230
+ this.playerDirection.y = 0;
231
+ this.playerDirection.normalize();
232
+ this.playerDirection.cross(this.camera.up);
233
+ return this.playerDirection;
234
+ };
235
+ // 玩家移动控制器
236
+ controls = (deltaTime) => {
237
+ const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8) * this.speed;
238
+ if (this.keyStates["KeyW"]) {
239
+ this.playerVelocity.add(this.getForwardVector().multiplyScalar(speedDelta));
240
+ }
241
+ if (this.keyStates["KeyS"]) {
242
+ this.playerVelocity.add(this.getForwardVector().multiplyScalar(-speedDelta));
243
+ }
244
+ if (this.keyStates["KeyA"]) {
245
+ this.playerVelocity.add(this.getSideVector().multiplyScalar(-speedDelta));
246
+ }
247
+ if (this.keyStates["KeyD"]) {
248
+ this.playerVelocity.add(this.getSideVector().multiplyScalar(speedDelta));
249
+ }
250
+ if (this.playerOnFloor &&
251
+ !this.keyStates["KeyW"] &&
252
+ !this.keyStates["KeyS"] &&
253
+ !this.keyStates["KeyA"] &&
254
+ !this.keyStates["KeyD"]) {
255
+ if (!this.inertia) {
256
+ this.playerVelocity.x *= 0.85;
257
+ this.playerVelocity.z *= 0.85;
258
+ }
259
+ }
260
+ if (this.playerOnFloor) {
261
+ if (this.keyStates["Space"]) {
262
+ this.playerVelocity.y = 15;
263
+ this.jumping = true;
264
+ this.dispatchEvent({ type: "jump_start" });
265
+ }
266
+ }
267
+ };
268
+ // 更新玩家的位置
269
+ updatePlayer = (deltaTime) => {
270
+ let damping = Math.exp(-4 * deltaTime) - 1;
271
+ if (!this.playerOnFloor) {
272
+ this.playerVelocity.y -= this.GRAVITY * deltaTime;
273
+ damping *= 0.1;
274
+ }
275
+ this.playerVelocity.addScaledVector(this.playerVelocity, damping);
276
+ const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime);
277
+ this.playerCollider.translate(deltaPosition);
278
+ this.playerCollisions();
279
+ // 设置相机位置(第一人称默认值)
280
+ this.camera.position.copy(this.playerCollider.end);
281
+ // 第三人称:偏移相机位置 + 朝向第一人称坐标
282
+ if (this.thirdPerson) {
283
+ const cosPitch = Math.cos(this._thirdPersonAngleX);
284
+ this.camera.position.x +=
285
+ Math.sin(this._thirdPersonAngleY) *
286
+ cosPitch *
287
+ this.thirdPersonDistance;
288
+ this.camera.position.y +=
289
+ Math.sin(this._thirdPersonAngleX) * this.thirdPersonDistance +
290
+ this.thirdPersonHeight;
291
+ this.camera.position.z +=
292
+ Math.cos(this._thirdPersonAngleY) *
293
+ cosPitch *
294
+ this.thirdPersonDistance;
295
+ this.camera.lookAt(this.playerCollider.end);
296
+ }
297
+ this.camera.rotation.x = clamp(this.camera.rotation.x, degToRad(-89), degToRad(89));
298
+ };
299
+ // 碰撞检测:6 射线迭代推离(水平 4 + 垂直 2)
300
+ playerCollisions() {
301
+ this.playerOnFloor = false;
302
+ // 收集 fpsCollision 物体
303
+ this._getColliders();
304
+ // 累积位移增量(用于迭代推离)
305
+ const positionIncremental = this._tempVec4.set(0, 0, 0);
306
+ // dev 模式:记录射线数据
307
+ const devRayData = this.dev ? [] : null;
308
+ let iterationCount = 0;
309
+ let hasCollision = true;
310
+ // ========== 迭代推离 ==========
311
+ while (hasCollision && iterationCount < this.MAX_ITERATIONS) {
312
+ hasCollision = false;
313
+ iterationCount++;
314
+ // 最后 2 次迭代记录调试数据
315
+ const recordDebug = this.dev && iterationCount >= this.MAX_ITERATIONS - 2;
316
+ // 获取胶囊体中心的世界位置
317
+ const centerPos = this._tempVec3
318
+ .addVectors(this.playerCollider.start, this.playerCollider.end)
319
+ .multiplyScalar(0.5);
320
+ // ========== 水平射线(4 根 45°)==========
321
+ for (let ri = 0; ri < this.HORIZONTAL_RAY_DIRECTIONS.length; ri++) {
322
+ const rayDir = this.HORIZONTAL_RAY_DIRECTIONS[ri];
323
+ // 起点:胶囊体中心向射线方向偏移 COLLIDE_VOLUME
324
+ const rayOrigin = this._tempVec2
325
+ .copy(centerPos)
326
+ .addScaledVector(rayDir, -this.COLLIDE_VOLUME);
327
+ this._raycaster.set(rayOrigin, rayDir.clone());
328
+ this._raycaster.far = this.COLLIDE_VOLUME * 2;
329
+ const hits = this._raycaster.intersectObjects(this._colliders, false);
330
+ if (recordDebug) {
331
+ // 记录调试射线数据
332
+ const farPoint = new Vector3().copy(rayOrigin).addScaledVector(rayDir, this.COLLIDE_VOLUME * 2);
333
+ const hasHit = hits.length > 0 && hits[0].distance < this.COLLIDE_VOLUME * 2;
334
+ devRayData.push({
335
+ origin: rayOrigin.clone(),
336
+ farPoint: hasHit ? hits[0].point.clone() : farPoint,
337
+ hit: hasHit,
338
+ });
339
+ }
340
+ if (hits.length > 0) {
341
+ const hit = hits[0];
342
+ // 推离向量:交点 → 当前位置,长度 PUSH_DISTANCE
343
+ const pushVector = this._tempVec
344
+ .copy(centerPos)
345
+ .sub(hit.point)
346
+ .setLength(this.PUSH_DISTANCE);
347
+ // 累加推离量到位移增量
348
+ positionIncremental.add(pushVector);
349
+ // 继续循环,因为推离后可能还会碰撞
350
+ hasCollision = true;
351
+ }
352
+ }
353
+ // 重置 far(垂直射线各自设 far)
354
+ this._raycaster.far = Infinity;
355
+ // 垂直射线
356
+ // 向下 → 地面检测
357
+ const downRayOrigin = centerPos.clone();
358
+ this._raycaster.set(downRayOrigin, this.VERTICAL_DOWN.clone());
359
+ this._raycaster.far = this.COLLIDE_VOLUME + 0.5; // 适当延长检测距离
360
+ const downHits = this._raycaster.intersectObjects(this._colliders, false);
361
+ if (downHits.length > 0) {
362
+ const hit = downHits[0];
363
+ // 脚下 COLLIDE_VOLUME 内 = 站在地面
364
+ if (hit.distance < this.COLLIDE_VOLUME + 0.1) {
365
+ this.playerOnFloor = true;
366
+ // 如果正在下落,归零垂直速度
367
+ if (this.playerVelocity.y < 0) {
368
+ this.playerVelocity.y = 0;
369
+ }
370
+ // 上移站位
371
+ const adjustY = this.COLLIDE_VOLUME + 0.1 - hit.distance;
372
+ if (adjustY > 0) {
373
+ positionIncremental.y += adjustY;
374
+ }
375
+ }
376
+ else if (hit.distance < this.COLLIDE_VOLUME + 0.3) {
377
+ // 接近地面缓冲
378
+ this.playerOnFloor = true;
379
+ if (this.playerVelocity.y < 0) {
380
+ this.playerVelocity.y = 0;
381
+ }
382
+ }
383
+ hasCollision = true;
384
+ }
385
+ if (recordDebug) {
386
+ // 向下射线调试数据
387
+ const downFar = new Vector3().copy(downRayOrigin).addScaledVector(this.VERTICAL_DOWN, this.COLLIDE_VOLUME + 0.5);
388
+ const downHit = downHits.length > 0;
389
+ devRayData.push({
390
+ origin: downRayOrigin.clone(),
391
+ farPoint: downHit ? downHits[0].point.clone() : downFar,
392
+ hit: downHit,
393
+ });
394
+ }
395
+ // 向上 → 头顶碰撞
396
+ const upRayOrigin = centerPos.clone();
397
+ this._raycaster.set(upRayOrigin, this.VERTICAL_UP.clone());
398
+ this._raycaster.far = this.COLLIDE_VOLUME + 0.2;
399
+ const upHits = this._raycaster.intersectObjects(this._colliders, false);
400
+ if (upHits.length > 0) {
401
+ const hit = upHits[0];
402
+ // 头顶碰撞:归零向上速度 + 下移
403
+ if (hit.distance < this.COLLIDE_VOLUME + 0.2) {
404
+ // 归零向上的速度
405
+ if (this.playerVelocity.y > 0) {
406
+ this.playerVelocity.y = 0;
407
+ }
408
+ // 计算需要下移的距离
409
+ const adjustY = this.COLLIDE_VOLUME + 0.2 - hit.distance;
410
+ if (adjustY > 0) {
411
+ positionIncremental.y -= Math.min(adjustY, 0.01); // 限制每次调整量
412
+ }
413
+ hasCollision = true;
414
+ }
415
+ }
416
+ if (recordDebug) {
417
+ // 向上射线调试数据
418
+ const upFar = new Vector3().copy(upRayOrigin).addScaledVector(this.VERTICAL_UP, this.COLLIDE_VOLUME + 0.2);
419
+ const upHit = upHits.length > 0;
420
+ devRayData.push({
421
+ origin: upRayOrigin.clone(),
422
+ farPoint: upHit ? upHits[0].point.clone() : upFar,
423
+ hit: upHit,
424
+ });
425
+ }
426
+ }
427
+ // ========== 更新调试射线(dev 模式)==========
428
+ if (this.dev && devRayData) {
429
+ this._initDebugLines();
430
+ for (let i = 0; i < devRayData.length && i < 6; i++) {
431
+ const data = devRayData[i];
432
+ // 跳过未收集到的(可能迭代次数不够)
433
+ this._updateDebugLine(i, data.origin, data.farPoint, data.hit);
434
+ }
435
+ }
436
+ // 应用推离
437
+ if (positionIncremental.lengthSq() > 0) {
438
+ this.playerCollider.translate(positionIncremental);
439
+ }
440
+ // 事件分发
441
+ if (this.jumping && !this.prePlayerOnFloor && this.playerOnFloor) {
442
+ this.jumping = false;
443
+ this.dispatchEvent({ type: "jump_end" });
444
+ }
445
+ const { x: preX, z: preZ } = this.prePlayerVelocity;
446
+ const { x, z } = this.playerVelocity;
447
+ if (Math.abs(preX + preZ) < 0.1 && Math.abs(x + z) > 0.1) {
448
+ this.dispatchEvent({ type: "move_start" });
449
+ }
450
+ else if (Math.abs(preX + preZ) > 0.1 && Math.abs(x + z) < 0.1) {
451
+ this.dispatchEvent({ type: "move_end" });
452
+ }
453
+ this.prePlayerVelocity.copy(this.playerVelocity);
454
+ this.prePlayerOnFloor = this.playerOnFloor;
455
+ }
456
+ // 越界传送
457
+ teleportPlayerIfOob = () => {
458
+ if (this.camera.position.y <= -10) {
459
+ this.playerCollider.start.set(0, 0.35, 0);
460
+ this.playerCollider.end.set(0, 1, 10);
461
+ this.playerCollider.radius = 0.35;
462
+ this.camera.position.copy(this.playerCollider.end);
463
+ }
464
+ };
465
+ // 更新
466
+ update() {
467
+ if (this.enable) {
468
+ // 第三人称模式切换时同步角度
469
+ if (this.thirdPerson && !this._prevThirdPerson) {
470
+ // 从第一人称切换到第三人称:复制相机旋转角
471
+ this._thirdPersonAngleY = this.camera.rotation.y;
472
+ this._thirdPersonAngleX = this.camera.rotation.x;
473
+ }
474
+ else if (!this.thirdPerson && this._prevThirdPerson) {
475
+ // 从第三人称切换回第一人称:还原相机旋转角
476
+ this.camera.rotation.y = this._thirdPersonAngleY;
477
+ this.camera.rotation.x = this._thirdPersonAngleX;
478
+ }
479
+ this._prevThirdPerson = this.thirdPerson;
480
+ const deltaTime = Math.min(0.05, this.clock.getDelta()) / this.STEPS_PER_FRAME;
481
+ for (let i = 0; i < this.STEPS_PER_FRAME; i++) {
482
+ this.controls(deltaTime);
483
+ this.updatePlayer(deltaTime);
484
+ this.teleportPlayerIfOob();
485
+ }
486
+ }
487
+ // dev 模式关闭时隐藏调试射线
488
+ if (!this.dev && this._debugRayGroup && this._debugRayGroup.visible) {
489
+ this._debugRayGroup.visible = false;
490
+ }
491
+ else if (this.dev && this._debugRayGroup && !this._debugRayGroup.visible) {
492
+ this._debugRayGroup.visible = true;
493
+ }
494
+ }
495
+ // 销毁事件
496
+ disposer() {
497
+ this.enable = false;
498
+ document.removeEventListener("keydown", this.keydown);
499
+ document.removeEventListener("keyup", this.keyup);
500
+ this.canvas.removeEventListener("pointerdown", this.mousedown);
501
+ document.removeEventListener("pointerup", this.mouseup);
502
+ document.body.removeEventListener("pointermove", this.mousemove);
503
+ this._disposeDebugLines();
504
+ }
505
+ }