three-player-controller 0.3.7 → 0.3.8
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 +88 -34
- package/dist/index.d.mts +60 -81
- package/dist/index.d.ts +60 -81
- package/dist/index.js +1163 -1793
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1163 -1793
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/playerController.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as THREE4 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";
|
|
@@ -21,6 +21,231 @@ var vehicle_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAY
|
|
|
21
21
|
// assets/imgs/view.png
|
|
22
22
|
var view_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAQAElEQVR4AeydC3qkthKFm7uxZFY2npVNsrK+5+9IHoyhKYEEetR8VKBpocep+lUCY+d/D//nCrgCmwo4IJvS+BeuwOPhgHgUuAJvFHBA3ohz5qvn8/l3sA/tsd/aL43zc/v7TJt+bX4FHJAMmirwgYFABwB9fD5V7e9gP7XHCP6lcX5un9erEo4/tP/Q9b7dpEBBQG4a0UXNKnCBgiCOMBDoAJCrB9RFnT/VFhttOSy51DXW44AYhaKYonQJBUHMV1cYbTksVyg9a8MBmYmxdRjB0PcsmwhUHd660YcIi2eVgq5wQN6IWyEYa711UNZUyXTOAVkRshEwlj13UJaKZPjcJiAZBr5VheBgGYWxjNkqVvN5ByWjdxyQIKbA4JEqT6RaBSOM5HMHKH5/8inHsQMHRLoBh3Y8UtUu6/aPasN+aR/th46x+DnuKaevsm5AwuPhXqDPKo6lsqEBERivx7YSKgccBDhG8P+Y/vvHHvvQx2j/6BiLn+OecvpqmtSfnNAAB5B4NpGwqduwgACHxMpxr/GCYpomAhwj+Dmn6o9t0zR9gUa1AMypOlUH2cQhkRAp25CACA4CBTi+aWU8QbD+UCCzseez8dL0YmoEYF6ZSVcDi3aHNockUbbhAAlwHF1SAQJAYBwnyn2uuEAhOwHLpJqOguKQSDzrNhQgJ+AABqDAOLbqW6ycYDkDCpCcyaDFxlVbxcMAcgKOXwrGasBYBpD6xnLxSDbhAQXXLqv0zzMFhgBEcPAk58iyCjCqDyIgkR1ZdpFJqh/fLF4vPxwCEKmaupxgrU/M5VxOqRtlN3WYYE/NJg7JG7d0D4iyRyocryXVG82q/uoEJGTZqsd2R+e6BkRwMKOmOB44uOYOX2RrM0DCY+GUOo8sQVPqb7Jst4AEOFKc3gUcMQoFCcvDFEi4aU/NtrG5bvfdAiKPDQuHxv7aDkKSknFf7fT8ny4BCdnD6rcOMsf2UA9AkjKxbDfcyTfdASI4mAGtTu4ajhijARLr0y2WWs3fh8Wxn913B4gEscLxUOAMEwhhrNyXSKLdjUe/TDS7BXsv0BUgyh4EvNWx1hm1mxgQJNy0myHpZuAnBtIVIAk6DLG02tDDOjGw1LJONhtNtX+6G0BC9jAtrzSTkmna996BEWjsZBDMcvXPh6VUx2W6ASTBR9YZNKHK5opaNRg+i/QEiGcPI6chi1ghMelqbLq5Yl0AEpZXFvGtQWGpq+kygmTYZWaK47oARAM2zXIeFFLq62a5Fxl6mdU8IJ49vkZ84idrRjVNQIltP1oo3zwgVpE9e3xXSpqQQbDvX349M+zj3h4A+eurL1c/WWfK1Ys7P2nSRpl6SEh6AGRIx+WCNmSRXNV1V0/TgGhWMz2JURCYynXnXfuALMusIe9DmgbE6H+L841VdVvMssxqKVNnc1TrgFjuP/7NptbgFSljDwdJ64AM57ASjGoJ6ll2Q9jWAdkY1p/Tcr7ff/yR492RQ7KiTrOAjJjuV/x39anhbtSbBcQYGT4rGoVSMb9XkwjL7TsgyxL1fvb7j7y+8clkRc+WAVkZzrdTPit+k8RPpCjQOyApWnhZV+CbAg7IN0n8xBsFhlvWOiBvosG/cgUuBcTlbl6B4W7kewfE8ipK81HrAyinQO+AlFOuv5qHu7+wuLBlQIZL9xaHepm8CrQMiEUJnxUtKtnLDPdzpWYBmaZpnkHsLvaSWwr4/dqKMs0CsjKW1VP+UuOqLGsnLdl2uEmpdUAsDrM4fi1ghjlnnURGzNqtA2IJYl867Kvkk8iGRq0D4r9LveHYAqctWhdo9t4qmwbEmvKtS4h1VwxxdrhfhLJ6tWlAwiAt9yEeAEGs5U6Th/VXki06L6tv/nMPgFic4Gtsi0pvyliz9ZsqmvyqB0BMa2PNlA7JeohasqtJ4/Xq2z7bPCBhZrOkf0sgtO3NxN5r0rAurxJr7qd484AkuKK+/89FQucLFTVNGpqEhgWpF0CsSwBTQBQKxqqqTcgeVm2rGl+uznQBiGY4lljYni5kkWFnw4U4pslC2g6tVxeABMdbZzpTYIQ6u9x59rC7tRtANNORQbDd0ScEyG5drRUIYzdNEtJ06OyBb7sBhMHIzFlEgdL1Y19psbWZ4NDFVi1VtN+tK0A045FBMIvHfo8GicZrzgjS0lzWInarZboCJDghZeazzqah6nZ3AQ7reFM0bFcUQ8+7A0QzHxnE6uAhnmolwvGQhp49AjzdAcK4Eh38MwQQl3ZnGhv3WtbMwfitkwtlu7cuAQle+xH2ll2XkAQ4flsECGV+GSaXUHSMXbeAyNEpSy283RUkB+DwpRVRsLBuAWGcgoS1NKDw0WJdQHIEDomTknFVfIyta0CCC1PX1E1DIjiYFFKWVcjE0iplIuGaIax7QJRFcHzq7AgkirUnwdZMIKjDgJFyQ87YgKOpcdLpq6x7QBAyQJKaSbgUUKoPHoHB4+qnOswTK+3MW2VwmPt9WcEhAEFNQUKgH4VEMfjkeqqqxtQpwCBrYKn9cjgMig0DCFqcgITLX9lEQXk7KOrDHIzUrMFY/glacOz2RoGhAEGHEBhHMgmXs74HFN7juhyUDGAwBjJH6j0Z1w1pwwGClwMkZ4KEWRtQFLPPD/2Hz1Sd3ag7GMso7ExbwPGRvZMdVzgkIPhTkMSnW+w5ddTIKmQUxfETWLAzQfxQRa8llPYAEe1UnRrc2HBIgCPbsIAgFpDIyCRHl1xUMzdgwSIw7AFmaQCAxfOUi/ZUhUABEJg+ntqYAH5onB+nahn04qEBiT4PwZMLklgtewIcYJYGAFg8T7loXJfLyBrAASS56hyqHgckuBtIZJM+lgBF1V66AQRgeNY4KbsDshBQkBBULUPiWWPh0zMfHZAV9YBE1lo28ayx4suzpyyAnG2j2esFyYesdlDIGHSTJRWQNKt3jR13QAxeUfTNQalh+QUIAKGuTSwJDaPwIkcUcEASVFM0Ago26TJAwXR4yQYUtAcYGJ8vaXjkRhyQg95fwBJ/lpIzaKkLAwY1N7EHTs4d7LVflqqAA5Kq2Er5aZpeL/9pTxBrN00qFqFh1o9GcC8tfkf5l03//aMujPKqzrc7FLgZkDuGfE2bivEIDbN+NAJ+afE7yr/smh56KxYFHBCLSl5mWAUckGFd7wO3KOCAWFQylHk+n7x8iPECYnzxcLlXsS/b/Huumxt18X6WoXUvUkoBB+SAsgpxgpdgjgEe38CNLyAS2Gu2bG1eJr64GPfURf1q7rXRHsY1y3r8cyEF+gUko2AKT4AgWLEIA4FMsGIZW9usivawVx/UJ/YA87F5hX9xWgEHZEVCBR9AYARhBAIQsJUrbjlFXwAm/majuv10WDK7wgGZCaoIe0GhU6/ljfYEoXbNbBEWwHZYMrhteEAiFNo/pSdgtAaFuv1tYwwRFl+GfZPHfmJYQATEPFsQUHbV2io5X4Z5Vkn03XCA5AAjUeOain9mlZo6VXNfhgFkcDCWMeigLBXZ+Nw9IBeDwYuF0XgJ8fXyobT/sp/0b3GOsnOLdahY0c1B2ZG3W0AuACMG8Sv4FfNs8xcReQnx9fKhvviyxyeLc5SdW6xn+VYwbXJ5bnNQNhTtEhDBwc1o7idSBCdG8Cq+J/bYK/g39D19evrzKj0A0d4SmtNtzCoAFJ569fzQYjbc/cOuABEY8ckUT272R79f4gWEihGY0TinU0U2U6ULaACG5VmufqEdP0dxSOSNbgARHDmzBsH2CQQBKa2q3dS/eXYBlhx9BZQc9TRdR/OACIxcWSNCoXibgIPPzTl3miZgmdRxQMF0eGhD1+GzSNOAAIdcf/ZeAxB+KLAwjlVl+5vGAyhYhKXaQeFHGcs6jHsgVgNV9LdZQCQoIgLHUSGBASgwjo/WU/11AZYqQZEf8SFGtsJY2r0eFtQgbJOABFER8oiGwAAUGMdH6mjwmscDUB6PB8suTIfvN5Uvqo/8yCT390YvgGTru41L8p9uDhCJGmebI2rwVwiHA2MulIKeZReBuQcJP9+ZX1rieG+Sw9cl2jXX2QwgAoObRgQ7MqswEwIGgWEWp+eCAZS47EKfOFyO0Yp9PHfbXn7H57e13wQgEgkoEIp9qlg4G6vC4amdL10+gII+OnxtHNekFRPjEb9nka56QGZwpA4YJ9fm7NQx9F4eH1nGyORoKZe9TNWAnIBj+HuN7JHyrsLj3+3dB33WrFi4BZJqAZEgpNUjopA1/F7jM7TqPdCCjgxiheSWpVa1gMitqXAgNnCw1+W+taCAIGEys/osNSZOS1AlIMoeqULwRq3DcTocbqvAmkUeB2Lj1KCqAyQIwPLKOrAXHNbCXq4+BZRFyCBWSC5dalUFiOAg3abA8boZr8/l3qNUBQQJvgeUL5dufNj7AePGZemnqwEkwJEycOBA1PRR+xW1KpCSRS7xfRWACA6yhsNRa9he1C9lETKIFZJL3tWqAhDpn3JTzj3HJbOH+uXbxQoIEnwLKJaWUyZVS33fytwOiLIHgnzr2MYJ4LjiJbqN5v30RQpYswg37Cnxk9z9WwEJcJhnAc0uDkeyi9u7QH4mg2CWzh9dalnqftwKiHpohkNlHQ6JMMomSFL8nRJHSRLeBkjIHtbO8sTKOqNY6/Ry9StghaTYUusWQAIcVuqBo+g6s/44GbOHyiJMiphFgCJLrVsA0WitcDwkksMhwUbd5H9rFkEic1xR2GKXAxKyh6VvlEkRh/JufSpgjYPsS61jgJxzgpVyllbW9HquR3511QooixAHmKWfWZdalwKSkj0kii+tLOEwSBnFgzWLoIh1EqbsW7sUEPXE2nHrD4pUpW8DKWCNC15dyiLLZYAkZA+WVp49sri3r0qURYgL01JL8ZYFkssAkatM2SOIoOK+uQKrClyaRS4BRDRD/upoFyd/LT4391Fj5UkKf2PW7fnMroECwjTRqlyW7RJA1FPToFrPHk8FhMbKm8mkd7fHo5QGD8M/01Jsr57igChohsgeYZwExJ7m/v0FCmiybQMQaTFE9rCOU+V8K69AFjjoZtEMEmZV2tkz643XXj3+vSuAAv/ynxxWFBBrB5UOrcswa5Vr5fzcGApk/TFBaUAsy6teske2tD5GHBcZJb9xmnWyLQZIwvKqiFI3VNoL6DdIl6VJMkfK6yimRosBotb/ku1uvSyvNA4yCA5ivztuL5BNAfTmr2pmzRyxdyUBsTzy7GrWBRIZzuJ/TAMsbo9HMQ2kNRt6A0mM6az7IoBYl1caXRHqsypkqux7IY2N9bDbNBXT4Lvq+c8UAcTYzWLUG9v3Yq7ArgKlALE8vcr2rHp3lF7AFTioQHZAtLyy3HvQXc8gqOBWtQLZAdFoTYCwRldZ31yBqhUoAYhlwF09vbIM+HAZv/BWBUoAYrn/uHXQ3rgrYFWgBCCWtoe5/+CebGYfOnZ7PotoYAm81DJZAZHz/f4jeAAtZPzy1NzIrm6PRxEN3xxFmgAAAvRJREFUpDfbR3BBll1WQNQjCyDdZw95CScBhkUPyeZbRgV+Bv2zVJkbEEunuv75R3AOM6RFi1vLdNw4kGSZnHID8lfHou8OzeHYlai5ArkBsVDb8xLLM0c9CFhicbe3uQHZbbDXAiF79Dq8FseVZSLOBogCxERsxz9B9+xRD0b88lRdgNSjjfekAgXu7AKv1/MUMUsfsmUQY2+yUG1sq8ZijN/t8SihAa8v8ctT/ILWI9e/qwHJ1e9W6/lXS8yXE30/5dbhY5omwMsaGw5IVjl3K8v2fH63JS+QRYGcgJhu0rP0us5KrLOX38zX6b/VXuUEZLWBgU6yBrYMl7/+nu0m0tJgX2WuHc3VgHT7mklY/5qziPWx+LXh4K0tFcgJiDU4ln3o6bM1izBmX2qhQuWWE5DdoWqW7XppofExSVgh8aXWbsTcXyAbIIbgsAbO/aqc6IF0YBIAFEst/lTLotKNZbIBwhhCcKyBwI/+CRyKjWBrGmyN25daW8pcfX6lvayAUD+QyF6bPvPDII5HguOhAZNBrJD4UkuBUuuWHZD5QEOgzE8Nc6yxMykAimXMvtSyqHRDmaKA3DCe2pq0ZhH67UstVKjMHJCCDlEWIYNYIWGpNfrbCAW9caxqB+SYbuarBEnKUos/9GCu2wuWVyAXIOV72nYL1izy0E/YHZKKfO2AXOAMZRFfal2gc4kmHJASqq7UKUh8qbWiS+2nHJBrPeRLrWv1Pt2aA3JaQnsFyiK+1LLLVUXJBgCpQqdsnRAkvtTKpmb5ihyQ8hqvtZCy1AKotTr83AUKOCAXiLxsQlkkZanlr6EsBbzwswNyodjzpgQJmQFQ5qf9uDIFHJB7HWJdavkrKDf5aWxAbhI9NqssQgbB4infV6aAA3KzQwTJ7l8CVBmWYzf3dMzmHZA6/P4Oknff1dH7jnvhgFTgXGUI/uDypK5wT8KSC4u/jcmxvvLtDgUckDtU32hToPD3ZQEDczA2dLrytANSSG2vtg8F/g8AAP//r/s6dQAAAAZJREFUAwCqeBInE6huXwAAAABJRU5ErkJggg==";
|
|
23
23
|
|
|
24
|
+
// src/utils/mobileControls.ts
|
|
25
|
+
var MobileControls = class {
|
|
26
|
+
constructor(setInput, controls) {
|
|
27
|
+
// 摇杆状态
|
|
28
|
+
this.nippleModule = null;
|
|
29
|
+
this.joystickManager = null;
|
|
30
|
+
this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
|
|
31
|
+
// DOM 元素
|
|
32
|
+
this.joystickZoneEl = null;
|
|
33
|
+
this.lookAreaEl = null;
|
|
34
|
+
this.jumpBtnEl = null;
|
|
35
|
+
this.flyBtnEl = null;
|
|
36
|
+
this.viewBtnEl = null;
|
|
37
|
+
this.vehicleBtnEl = null;
|
|
38
|
+
// 触摸状态
|
|
39
|
+
this.lookPointerId = null;
|
|
40
|
+
this.isLookDown = false;
|
|
41
|
+
this.lastTouchX = 0;
|
|
42
|
+
this.lastTouchY = 0;
|
|
43
|
+
// 触摸按下
|
|
44
|
+
this.onPointerDown = (e) => {
|
|
45
|
+
if (e.pointerType !== "touch") return;
|
|
46
|
+
this.isLookDown = true;
|
|
47
|
+
this.lookPointerId = e.pointerId;
|
|
48
|
+
this.lastTouchX = e.clientX;
|
|
49
|
+
this.lastTouchY = e.clientY;
|
|
50
|
+
this.lookAreaEl?.setPointerCapture?.(e.pointerId);
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
};
|
|
53
|
+
// 触摸移动
|
|
54
|
+
this.onPointerMove = (e) => {
|
|
55
|
+
if (!this.isLookDown || e.pointerId !== this.lookPointerId) return;
|
|
56
|
+
const dx = e.clientX - this.lastTouchX;
|
|
57
|
+
const dy = e.clientY - this.lastTouchY;
|
|
58
|
+
this.lastTouchX = e.clientX;
|
|
59
|
+
this.lastTouchY = e.clientY;
|
|
60
|
+
this.setInput({ lookDeltaX: dx, lookDeltaY: dy });
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
};
|
|
63
|
+
// 触摸抬起
|
|
64
|
+
this.onPointerUp = (e) => {
|
|
65
|
+
if (e.pointerId !== this.lookPointerId) return;
|
|
66
|
+
this.isLookDown = false;
|
|
67
|
+
this.lookPointerId = null;
|
|
68
|
+
this.lookAreaEl?.releasePointerCapture?.(e.pointerId);
|
|
69
|
+
};
|
|
70
|
+
this.setInput = setInput;
|
|
71
|
+
this.controls = controls;
|
|
72
|
+
}
|
|
73
|
+
// 初始化移动端控制
|
|
74
|
+
async init() {
|
|
75
|
+
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
76
|
+
this.controls.touches = { ONE: null, TWO: null };
|
|
77
|
+
this.nippleModule = await import("nipplejs");
|
|
78
|
+
const nipple = this.nippleModule?.default;
|
|
79
|
+
const JOY_SIZE = 120;
|
|
80
|
+
const container = document.body;
|
|
81
|
+
this.joystickZoneEl = document.createElement("div");
|
|
82
|
+
this.joystickZoneEl.id = "joy-zone";
|
|
83
|
+
Object.assign(this.joystickZoneEl.style, {
|
|
84
|
+
position: "absolute",
|
|
85
|
+
left: "16px",
|
|
86
|
+
bottom: "16px",
|
|
87
|
+
width: `${JOY_SIZE + 40}px`,
|
|
88
|
+
height: `${JOY_SIZE + 40}px`,
|
|
89
|
+
touchAction: "none",
|
|
90
|
+
zIndex: "999",
|
|
91
|
+
pointerEvents: "auto",
|
|
92
|
+
WebkitUserSelect: "none",
|
|
93
|
+
userSelect: "none"
|
|
94
|
+
});
|
|
95
|
+
container.appendChild(this.joystickZoneEl);
|
|
96
|
+
this.blockTouch(this.joystickZoneEl);
|
|
97
|
+
this.joystickManager = nipple.create({
|
|
98
|
+
zone: this.joystickZoneEl,
|
|
99
|
+
mode: "static",
|
|
100
|
+
position: { left: `${(JOY_SIZE + 40) / 2}px`, bottom: `${(JOY_SIZE + 40) / 2}px` },
|
|
101
|
+
color: "#ffffff",
|
|
102
|
+
size: JOY_SIZE,
|
|
103
|
+
multitouch: true,
|
|
104
|
+
maxNumberOfNipples: 1
|
|
105
|
+
});
|
|
106
|
+
this.joystickManager.on("move", (_evt, data) => {
|
|
107
|
+
if (!data) return;
|
|
108
|
+
const rawX = data.vector?.x ?? 0;
|
|
109
|
+
const rawY = data.vector?.y ?? 0;
|
|
110
|
+
const distance = data.distance ?? 0;
|
|
111
|
+
const deadzone = 0.5;
|
|
112
|
+
const dirX = rawX > deadzone ? 1 : rawX < -deadzone ? -1 : 0;
|
|
113
|
+
const dirY = rawY > deadzone ? 1 : rawY < -deadzone ? -1 : 0;
|
|
114
|
+
const isSprinting = distance >= JOY_SIZE / 2;
|
|
115
|
+
const prev = this.prevJoyState;
|
|
116
|
+
if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift) return;
|
|
117
|
+
this.prevJoyState = { dirX, dirY, shift: isSprinting };
|
|
118
|
+
this.setInput({ moveX: dirX, moveY: dirY, shift: isSprinting });
|
|
119
|
+
});
|
|
120
|
+
this.joystickManager.on("end", () => {
|
|
121
|
+
const prev = this.prevJoyState;
|
|
122
|
+
if (prev.dirX !== 0 || prev.dirY !== 0 || prev.shift) {
|
|
123
|
+
this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
|
|
124
|
+
this.setInput({ moveX: 0, moveY: 0, shift: false });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
this.lookAreaEl = document.createElement("div");
|
|
128
|
+
Object.assign(this.lookAreaEl.style, {
|
|
129
|
+
position: "absolute",
|
|
130
|
+
right: "0",
|
|
131
|
+
bottom: "0",
|
|
132
|
+
width: "50%",
|
|
133
|
+
height: "100%",
|
|
134
|
+
zIndex: "998",
|
|
135
|
+
touchAction: "none",
|
|
136
|
+
WebkitUserSelect: "none",
|
|
137
|
+
userSelect: "none"
|
|
138
|
+
});
|
|
139
|
+
container.appendChild(this.lookAreaEl);
|
|
140
|
+
this.blockTouch(this.lookAreaEl);
|
|
141
|
+
this.lookAreaEl.addEventListener("pointerdown", this.onPointerDown, { passive: false });
|
|
142
|
+
this.lookAreaEl.addEventListener("pointermove", this.onPointerMove, { passive: false });
|
|
143
|
+
this.lookAreaEl.addEventListener("pointerup", this.onPointerUp, { passive: false });
|
|
144
|
+
this.lookAreaEl.addEventListener("pointercancel", this.onPointerUp, { passive: false });
|
|
145
|
+
this.jumpBtnEl = this.createBtn(container, 14, 14, jump_default);
|
|
146
|
+
this.flyBtnEl = this.createBtn(container, 14, 14 + 80, fly_default);
|
|
147
|
+
this.viewBtnEl = this.createBtn(container, 14, 14 + 200, view_default);
|
|
148
|
+
this.vehicleBtnEl = this.createBtn(container, 14 + 100, 14 + 120, vehicle_default);
|
|
149
|
+
this.vehicleBtnEl.style.display = "none";
|
|
150
|
+
this.jumpBtnEl.addEventListener("touchstart", (e) => {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
this.setInput({ jump: true });
|
|
153
|
+
}, { passive: false });
|
|
154
|
+
this.jumpBtnEl.addEventListener("touchend", (e) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
this.setInput({ jump: false });
|
|
157
|
+
}, { passive: false });
|
|
158
|
+
this.jumpBtnEl.addEventListener("touchcancel", (e) => {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
this.setInput({ jump: false });
|
|
161
|
+
}, { passive: false });
|
|
162
|
+
this.flyBtnEl.addEventListener("touchstart", (e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
this.setInput({ toggleFly: true });
|
|
165
|
+
}, { passive: false });
|
|
166
|
+
this.viewBtnEl.addEventListener("touchstart", (e) => {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
this.setInput({ toggleView: true });
|
|
169
|
+
}, { passive: false });
|
|
170
|
+
this.vehicleBtnEl.addEventListener("touchstart", (e) => {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
this.setInput({ toggleVehicle: true });
|
|
173
|
+
}, { passive: false });
|
|
174
|
+
}
|
|
175
|
+
// 销毁移动端控制
|
|
176
|
+
destroy() {
|
|
177
|
+
try {
|
|
178
|
+
this.joystickManager?.destroy?.();
|
|
179
|
+
this.joystickManager = null;
|
|
180
|
+
if (this.lookAreaEl) {
|
|
181
|
+
this.lookAreaEl.removeEventListener("pointerdown", this.onPointerDown);
|
|
182
|
+
this.lookAreaEl.removeEventListener("pointermove", this.onPointerMove);
|
|
183
|
+
this.lookAreaEl.removeEventListener("pointerup", this.onPointerUp);
|
|
184
|
+
this.lookAreaEl.removeEventListener("pointercancel", this.onPointerUp);
|
|
185
|
+
}
|
|
186
|
+
[this.joystickZoneEl, this.lookAreaEl, this.jumpBtnEl, this.flyBtnEl, this.viewBtnEl, this.vehicleBtnEl].forEach((el) => el?.parentElement?.removeChild(el));
|
|
187
|
+
this.joystickZoneEl = this.lookAreaEl = this.jumpBtnEl = this.flyBtnEl = this.viewBtnEl = this.vehicleBtnEl = null;
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.warn("\u9500\u6BC1\u79FB\u52A8\u7AEF\u63A7\u5236\u65F6\u51FA\u9519\uFF1A", e);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// 同步车辆按钮显隐
|
|
193
|
+
syncVehicleBtn(show) {
|
|
194
|
+
if (this.vehicleBtnEl) this.vehicleBtnEl.style.display = show ? "block" : "none";
|
|
195
|
+
}
|
|
196
|
+
// 同步控制模式按钮
|
|
197
|
+
syncControllerModeBtn(mode) {
|
|
198
|
+
if (!this.flyBtnEl || !this.jumpBtnEl) return;
|
|
199
|
+
if (mode === 0) {
|
|
200
|
+
this.flyBtnEl.style.display = "block";
|
|
201
|
+
this.jumpBtnEl.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5),rgba(0,0,0,0.5)),url("${jump_default}")`;
|
|
202
|
+
} else {
|
|
203
|
+
this.flyBtnEl.style.display = "none";
|
|
204
|
+
this.jumpBtnEl.style.backgroundImage = `url(${break_default})`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// 阻止默认触摸
|
|
208
|
+
blockTouch(el) {
|
|
209
|
+
["touchstart", "touchmove", "touchend", "touchcancel"].forEach((name) => {
|
|
210
|
+
el.addEventListener(name, (e) => e.preventDefault(), { passive: false });
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// 创建圆形按钮
|
|
214
|
+
createBtn(container, rightPx, bottomPx, bgUrl) {
|
|
215
|
+
const btn = document.createElement("button");
|
|
216
|
+
Object.assign(btn.style, {
|
|
217
|
+
position: "absolute",
|
|
218
|
+
right: `${rightPx}px`,
|
|
219
|
+
bottom: `${bottomPx}px`,
|
|
220
|
+
width: "56px",
|
|
221
|
+
height: "56px",
|
|
222
|
+
zIndex: "1000",
|
|
223
|
+
borderRadius: "50%",
|
|
224
|
+
border: "2px solid black",
|
|
225
|
+
padding: "20px",
|
|
226
|
+
opacity: "0.95",
|
|
227
|
+
touchAction: "none",
|
|
228
|
+
fontSize: "14px",
|
|
229
|
+
userSelect: "none",
|
|
230
|
+
overflow: "hidden",
|
|
231
|
+
boxSizing: "border-box",
|
|
232
|
+
backgroundColor: "transparent",
|
|
233
|
+
backgroundRepeat: "no-repeat, no-repeat",
|
|
234
|
+
backgroundPosition: "center center, center center",
|
|
235
|
+
backgroundSize: "100% 100%, 80% 80%",
|
|
236
|
+
backgroundImage: `linear-gradient(rgba(0,0,0,0.5),rgba(0,0,0,0.5)),url("${bgUrl}")`
|
|
237
|
+
});
|
|
238
|
+
container.appendChild(btn);
|
|
239
|
+
["touchstart", "touchend", "touchcancel"].forEach((name) => {
|
|
240
|
+
btn.addEventListener(name, (e) => e.preventDefault(), { passive: false });
|
|
241
|
+
});
|
|
242
|
+
return btn;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/utils/vehicleLoader.ts
|
|
247
|
+
import * as THREE3 from "three";
|
|
248
|
+
|
|
24
249
|
// src/utils/pathPlanner.ts
|
|
25
250
|
import * as THREE from "three";
|
|
26
251
|
var PathNode = class {
|
|
@@ -34,6 +259,7 @@ var PathNode = class {
|
|
|
34
259
|
this.parent = null;
|
|
35
260
|
this.position = position.clone();
|
|
36
261
|
}
|
|
262
|
+
// 判断节点相等
|
|
37
263
|
equals(other) {
|
|
38
264
|
return this.position.distanceTo(other.position) < 0.01;
|
|
39
265
|
}
|
|
@@ -42,10 +268,12 @@ var PriorityQueue = class {
|
|
|
42
268
|
constructor() {
|
|
43
269
|
this.elements = [];
|
|
44
270
|
}
|
|
271
|
+
// 入队并排序
|
|
45
272
|
enqueue(item, priority) {
|
|
46
273
|
this.elements.push({ priority, item });
|
|
47
274
|
this.elements.sort((a, b) => a.priority - b.priority);
|
|
48
275
|
}
|
|
276
|
+
// 出队最小优先级
|
|
49
277
|
dequeue() {
|
|
50
278
|
return this.elements.shift()?.item;
|
|
51
279
|
}
|
|
@@ -55,6 +283,7 @@ var PriorityQueue = class {
|
|
|
55
283
|
contains(item, compareFn) {
|
|
56
284
|
return this.elements.some((e) => compareFn(e.item, item));
|
|
57
285
|
}
|
|
286
|
+
// 更新节点优先级
|
|
58
287
|
update(item, newPriority, compareFn) {
|
|
59
288
|
const index = this.elements.findIndex((e) => compareFn(e.item, item));
|
|
60
289
|
if (index !== -1) {
|
|
@@ -68,22 +297,15 @@ var PathPlanner = class {
|
|
|
68
297
|
this.debugLines = [];
|
|
69
298
|
this.debugPoints = [];
|
|
70
299
|
this.obstacleChecker = obstacleChecker;
|
|
71
|
-
this.config = {
|
|
72
|
-
debugEnabled: false,
|
|
73
|
-
scale: 1,
|
|
74
|
-
...config
|
|
75
|
-
};
|
|
300
|
+
this.config = { debugEnabled: false, scale: 1, ...config };
|
|
76
301
|
}
|
|
77
|
-
//
|
|
302
|
+
// 启发式距离
|
|
78
303
|
heuristic(a, b) {
|
|
79
304
|
return a.distanceTo(b);
|
|
80
305
|
}
|
|
81
|
-
// A
|
|
306
|
+
// A* 寻路主入口
|
|
82
307
|
findPath(start, goal) {
|
|
83
|
-
|
|
84
|
-
if (!this.obstacleChecker.isBlocked(start, goal)) {
|
|
85
|
-
return [goal];
|
|
86
|
-
}
|
|
308
|
+
if (!this.obstacleChecker.isBlocked(start, goal)) return [goal];
|
|
87
309
|
const navigationPoints = this.obstacleChecker.getNavigationNodes(start, goal);
|
|
88
310
|
const allNodes = [new PathNode(start), new PathNode(goal), ...navigationPoints.map((p) => new PathNode(p))];
|
|
89
311
|
if (allNodes.length < 2) {
|
|
@@ -97,45 +319,35 @@ var PathPlanner = class {
|
|
|
97
319
|
startNode.f = startNode.h;
|
|
98
320
|
const openList = new PriorityQueue();
|
|
99
321
|
const closedSet = /* @__PURE__ */ new Set();
|
|
100
|
-
openList.enqueue(startNode, startNode.f);
|
|
101
322
|
const nodeEquals = (a, b) => a.equals(b);
|
|
323
|
+
openList.enqueue(startNode, startNode.f);
|
|
102
324
|
while (!openList.isEmpty()) {
|
|
103
325
|
const current = openList.dequeue();
|
|
104
326
|
if (!current) break;
|
|
105
327
|
if (current.equals(goalNode)) {
|
|
106
328
|
const path = this.reconstructPath(current);
|
|
107
|
-
|
|
108
|
-
if (this.config.debugEnabled) {
|
|
109
|
-
this.visualizePath([start, ...path]);
|
|
110
|
-
}
|
|
329
|
+
if (this.config.debugEnabled) this.visualizePath([start, ...path]);
|
|
111
330
|
return path;
|
|
112
331
|
}
|
|
113
332
|
closedSet.add(current);
|
|
114
333
|
for (const neighbor of allNodes) {
|
|
115
334
|
if (closedSet.has(neighbor)) continue;
|
|
116
|
-
if (this.obstacleChecker.isBlocked(current.position, neighbor.position))
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
335
|
+
if (this.obstacleChecker.isBlocked(current.position, neighbor.position)) continue;
|
|
119
336
|
const tentativeG = current.g + current.position.distanceTo(neighbor.position);
|
|
120
337
|
if (tentativeG < neighbor.g) {
|
|
121
338
|
neighbor.parent = current;
|
|
122
339
|
neighbor.g = tentativeG;
|
|
123
340
|
neighbor.h = this.heuristic(neighbor.position, goalNode.position);
|
|
124
341
|
neighbor.f = neighbor.g + neighbor.h;
|
|
125
|
-
if (openList.contains(neighbor, nodeEquals))
|
|
126
|
-
|
|
127
|
-
} else {
|
|
128
|
-
openList.enqueue(neighbor, neighbor.f);
|
|
129
|
-
}
|
|
342
|
+
if (openList.contains(neighbor, nodeEquals)) openList.update(neighbor, neighbor.f, nodeEquals);
|
|
343
|
+
else openList.enqueue(neighbor, neighbor.f);
|
|
130
344
|
}
|
|
131
345
|
}
|
|
132
346
|
}
|
|
133
|
-
console.warn("A
|
|
347
|
+
console.warn("A* \u672A\u627E\u5230\u8DEF\u5F84\uFF0C\u4F7F\u7528\u76F4\u7EBF\u8DEF\u5F84");
|
|
134
348
|
return [goal];
|
|
135
349
|
}
|
|
136
|
-
|
|
137
|
-
* 重建路径
|
|
138
|
-
*/
|
|
350
|
+
// 重建路径
|
|
139
351
|
reconstructPath(endNode) {
|
|
140
352
|
const path = [];
|
|
141
353
|
let current = endNode;
|
|
@@ -143,14 +355,10 @@ var PathPlanner = class {
|
|
|
143
355
|
path.unshift(current.position.clone());
|
|
144
356
|
current = current.parent;
|
|
145
357
|
}
|
|
146
|
-
if (path.length > 0)
|
|
147
|
-
path.shift();
|
|
148
|
-
}
|
|
358
|
+
if (path.length > 0) path.shift();
|
|
149
359
|
return this.smoothPath(path);
|
|
150
360
|
}
|
|
151
|
-
|
|
152
|
-
* 路径平滑
|
|
153
|
-
*/
|
|
361
|
+
// 路径平滑优化
|
|
154
362
|
smoothPath(path) {
|
|
155
363
|
if (path.length <= 2) return path;
|
|
156
364
|
const smoothed = [path[0]];
|
|
@@ -168,38 +376,28 @@ var PathPlanner = class {
|
|
|
168
376
|
}
|
|
169
377
|
return smoothed;
|
|
170
378
|
}
|
|
171
|
-
|
|
172
|
-
* 可视化路径
|
|
173
|
-
*/
|
|
379
|
+
// 可视化路径
|
|
174
380
|
visualizePath(path) {
|
|
175
381
|
if (!this.config.scene || !this.config.debugEnabled) return;
|
|
176
382
|
this.clearVisualization();
|
|
177
383
|
const scale = this.config.scale || 1;
|
|
178
384
|
if (path.length > 1) {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const material = new THREE.LineBasicMaterial({
|
|
182
|
-
color: 65280,
|
|
183
|
-
linewidth: 3
|
|
184
|
-
});
|
|
185
|
-
const line = new THREE.Line(geometry, material);
|
|
385
|
+
const geometry = new THREE.BufferGeometry().setFromPoints(path.map((p) => p.clone()));
|
|
386
|
+
const line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 65280, linewidth: 3 }));
|
|
186
387
|
this.config.scene.add(line);
|
|
187
388
|
this.debugLines.push(line);
|
|
188
389
|
}
|
|
189
390
|
path.forEach((point, index) => {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
color: index === path.length - 1 ? 16711680 : 65280
|
|
193
|
-
|
|
194
|
-
const sphere = new THREE.Mesh(geometry, material);
|
|
391
|
+
const sphere = new THREE.Mesh(
|
|
392
|
+
new THREE.SphereGeometry(20 * scale),
|
|
393
|
+
new THREE.MeshBasicMaterial({ color: index === path.length - 1 ? 16711680 : 65280 })
|
|
394
|
+
);
|
|
195
395
|
sphere.position.copy(point);
|
|
196
396
|
this.config.scene.add(sphere);
|
|
197
397
|
this.debugPoints.push(sphere);
|
|
198
398
|
});
|
|
199
399
|
}
|
|
200
|
-
|
|
201
|
-
* 清除路径可视化
|
|
202
|
-
*/
|
|
400
|
+
// 清除路径可视化
|
|
203
401
|
clearVisualization() {
|
|
204
402
|
if (!this.config.scene) return;
|
|
205
403
|
this.debugLines.forEach((line) => {
|
|
@@ -215,15 +413,11 @@ var PathPlanner = class {
|
|
|
215
413
|
});
|
|
216
414
|
this.debugPoints = [];
|
|
217
415
|
}
|
|
218
|
-
|
|
219
|
-
* 更新配置
|
|
220
|
-
*/
|
|
416
|
+
// 更新配置
|
|
221
417
|
updateConfig(config) {
|
|
222
418
|
this.config = { ...this.config, ...config };
|
|
223
419
|
}
|
|
224
|
-
|
|
225
|
-
* 销毁
|
|
226
|
-
*/
|
|
420
|
+
// 销毁规划器
|
|
227
421
|
dispose() {
|
|
228
422
|
this.clearVisualization();
|
|
229
423
|
}
|
|
@@ -243,7 +437,7 @@ function createVehicleController(world, chassisBody, wheels, wheelsInfo) {
|
|
|
243
437
|
vehicle.setWheelAxleCs(index, wheel.axleCs);
|
|
244
438
|
vehicle.setWheelSuspensionRestLength(index, wheel.suspensionRestLength);
|
|
245
439
|
vehicle.setWheelRadius(index, wheel.radius);
|
|
246
|
-
vehicle.setWheelMaxSuspensionTravel(index, wheel.suspensionRestLength
|
|
440
|
+
vehicle.setWheelMaxSuspensionTravel(index, wheel.suspensionRestLength);
|
|
247
441
|
vehicle.setWheelSuspensionStiffness(index, 250);
|
|
248
442
|
vehicle.setWheelSuspensionCompression(index, 6);
|
|
249
443
|
vehicle.setWheelSuspensionRelaxation(index, 6);
|
|
@@ -255,8 +449,8 @@ function createVehicleController(world, chassisBody, wheels, wheelsInfo) {
|
|
|
255
449
|
vehicle.setWheelSideFrictionStiffness(index, 2);
|
|
256
450
|
});
|
|
257
451
|
const up = new THREE2.Vector3(0, 1, 0);
|
|
258
|
-
const
|
|
259
|
-
const
|
|
452
|
+
const wheelSteeringQuat = new THREE2.Quaternion();
|
|
453
|
+
const wheelRotationQuat = new THREE2.Quaternion();
|
|
260
454
|
function updateWheelVisuals() {
|
|
261
455
|
for (const [index, wheelObj] of wheels.entries()) {
|
|
262
456
|
if (!wheelObj) continue;
|
|
@@ -267,9 +461,9 @@ function createVehicleController(world, chassisBody, wheels, wheelsInfo) {
|
|
|
267
461
|
const steering = vehicle.wheelSteering(index) ?? 0;
|
|
268
462
|
const rotationRad = vehicle.wheelRotation(index) ?? 0;
|
|
269
463
|
wheelObj.position.y = connection - suspension;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
wheelObj.quaternion.copy(
|
|
464
|
+
wheelSteeringQuat.setFromAxisAngle(up, steering);
|
|
465
|
+
wheelRotationQuat.setFromAxisAngle(wheelAxleCs, rotationRad);
|
|
466
|
+
wheelObj.quaternion.copy(wheelSteeringQuat).multiply(wheelRotationQuat);
|
|
273
467
|
} catch (e) {
|
|
274
468
|
}
|
|
275
469
|
}
|
|
@@ -280,37 +474,247 @@ function createVehicleController(world, chassisBody, wheels, wheelsInfo) {
|
|
|
280
474
|
} catch {
|
|
281
475
|
}
|
|
282
476
|
}
|
|
477
|
+
return { vehicle, updateWheelVisuals, destroy };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/utils/vehicleLoader.ts
|
|
481
|
+
function getBbox(object) {
|
|
482
|
+
const bbox = new THREE3.Box3().setFromObject(object);
|
|
483
|
+
const center = new THREE3.Vector3();
|
|
484
|
+
const size = new THREE3.Vector3();
|
|
485
|
+
bbox.getCenter(center);
|
|
486
|
+
bbox.getSize(size);
|
|
487
|
+
return { bbox, center, size };
|
|
488
|
+
}
|
|
489
|
+
function createObstacleChecker(vehicleGroup, bbox, scale, playerScale) {
|
|
490
|
+
return {
|
|
491
|
+
// 射线检测路径是否被车辆遮挡
|
|
492
|
+
isBlocked(start, end) {
|
|
493
|
+
const vehiclePos = vehicleGroup.position;
|
|
494
|
+
const vehicleQuat = vehicleGroup.quaternion;
|
|
495
|
+
const center = new THREE3.Vector3();
|
|
496
|
+
const size = new THREE3.Vector3();
|
|
497
|
+
bbox.getCenter(center);
|
|
498
|
+
bbox.getSize(size);
|
|
499
|
+
center.applyQuaternion(vehicleQuat).add(vehiclePos);
|
|
500
|
+
const halfSize = size.clone().multiplyScalar(0.5 * scale);
|
|
501
|
+
const corners = [];
|
|
502
|
+
for (let x = -1; x <= 1; x += 2)
|
|
503
|
+
for (let y = -1; y <= 1; y += 2)
|
|
504
|
+
for (let z = -1; z <= 1; z += 2)
|
|
505
|
+
corners.push(
|
|
506
|
+
new THREE3.Vector3(halfSize.x * x, halfSize.y * y, halfSize.z * z).applyQuaternion(vehicleQuat).add(center)
|
|
507
|
+
);
|
|
508
|
+
const expandedBBox = new THREE3.Box3();
|
|
509
|
+
corners.forEach((c) => expandedBBox.expandByPoint(c));
|
|
510
|
+
expandedBBox.expandByScalar(100 * playerScale);
|
|
511
|
+
const direction = new THREE3.Vector3().subVectors(end, start);
|
|
512
|
+
const length = direction.length();
|
|
513
|
+
const ray = new THREE3.Ray(start, direction.normalize());
|
|
514
|
+
const intersects = ray.intersectBox(expandedBBox, new THREE3.Vector3());
|
|
515
|
+
return intersects !== null && start.distanceTo(intersects) < length;
|
|
516
|
+
},
|
|
517
|
+
// 生成绕行导航节点
|
|
518
|
+
getNavigationNodes(start, _goal) {
|
|
519
|
+
const nodes = [];
|
|
520
|
+
const vehiclePos = vehicleGroup.position;
|
|
521
|
+
const vehicleQuat = vehicleGroup.quaternion;
|
|
522
|
+
const bboxSize = new THREE3.Vector3();
|
|
523
|
+
bbox.getSize(bboxSize);
|
|
524
|
+
const fwd = new THREE3.Vector3(0, 0, 1).applyQuaternion(vehicleQuat);
|
|
525
|
+
const right = new THREE3.Vector3(1, 0, 0).applyQuaternion(vehicleQuat);
|
|
526
|
+
const halfLen = bboxSize.z / 2 * scale;
|
|
527
|
+
const halfWidth = bboxSize.x / 2 * scale;
|
|
528
|
+
const groundY = start.y;
|
|
529
|
+
for (const margin of [300 * playerScale, 500 * playerScale]) {
|
|
530
|
+
nodes.push(vehiclePos.clone().add(fwd.clone().multiplyScalar(halfLen + margin)).add(right.clone().multiplyScalar(-halfWidth - margin)).setY(groundY));
|
|
531
|
+
nodes.push(vehiclePos.clone().add(fwd.clone().multiplyScalar(halfLen + margin)).add(right.clone().multiplyScalar(halfWidth + margin)).setY(groundY));
|
|
532
|
+
nodes.push(vehiclePos.clone().add(fwd.clone().multiplyScalar(-halfLen - margin)).add(right.clone().multiplyScalar(-halfWidth - margin)).setY(groundY));
|
|
533
|
+
nodes.push(vehiclePos.clone().add(fwd.clone().multiplyScalar(-halfLen - margin)).add(right.clone().multiplyScalar(halfWidth + margin)).setY(groundY));
|
|
534
|
+
}
|
|
535
|
+
return nodes;
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
async function loadVehicleModel(opts, ctx) {
|
|
540
|
+
const { loader, scene, world, RAPIER, vehicleParams, vehicleLength, playerScale } = ctx;
|
|
541
|
+
const scale = opts.scale ?? 1;
|
|
542
|
+
const chassisRatio = opts.chassisRatio ?? 0.2;
|
|
543
|
+
const suspensionRestLengthRatio = opts.suspensionRestLengthRatio ?? 0.2;
|
|
544
|
+
const speedMultiplier = opts.speedMultiplier ?? 1;
|
|
545
|
+
vehicleParams.power.accelerateForce = 50 * scale;
|
|
546
|
+
vehicleParams.power.brakeForce = 200 * scale;
|
|
547
|
+
vehicleParams.power.maxSpeed = 1e4 * scale;
|
|
548
|
+
vehicleParams.followVehicleDirection = opts.followVehicleDirection ?? true;
|
|
549
|
+
const vehicleModel = await loader.loadAsync(opts.url);
|
|
550
|
+
const { size: originalSize } = getBbox(vehicleModel.scene);
|
|
551
|
+
const modelScale = vehicleLength / Math.max(originalSize.x, originalSize.y, originalSize.z);
|
|
552
|
+
const vehicleMixer = new THREE3.AnimationMixer(vehicleModel.scene);
|
|
553
|
+
const vehicleActions = /* @__PURE__ */ new Map();
|
|
554
|
+
const animations = vehicleModel.animations ?? [];
|
|
555
|
+
const openDoorClip = animations.find((a) => a.name === (opts.animations?.openDoorAnim ?? ""));
|
|
556
|
+
if (openDoorClip) {
|
|
557
|
+
const action = vehicleMixer.clipAction(openDoorClip);
|
|
558
|
+
action.setLoop(THREE3.LoopOnce, 1);
|
|
559
|
+
action.clampWhenFinished = true;
|
|
560
|
+
action.setEffectiveTimeScale(openDoorClip.duration);
|
|
561
|
+
action.enabled = true;
|
|
562
|
+
action.setEffectiveWeight(0);
|
|
563
|
+
vehicleActions.set("openDoor", action);
|
|
564
|
+
}
|
|
565
|
+
const wheelObjects = [];
|
|
566
|
+
for (const name of opts.wheelsNames) {
|
|
567
|
+
let found = false;
|
|
568
|
+
vehicleModel.scene.traverse((child) => {
|
|
569
|
+
if (child.name === name && !found) {
|
|
570
|
+
wheelObjects.push(child);
|
|
571
|
+
found = true;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
if (!found) console.warn(`\u672A\u627E\u5230\u8F6E\u5B50: ${name}`);
|
|
575
|
+
}
|
|
576
|
+
const tempGroup = new THREE3.Group();
|
|
577
|
+
scene.add(tempGroup);
|
|
578
|
+
vehicleModel.scene.scale.multiplyScalar(modelScale * scale);
|
|
579
|
+
vehicleModel.scene.rotateY(vehicleParams.model.rotation);
|
|
580
|
+
const { size, bbox, center } = getBbox(vehicleModel.scene);
|
|
581
|
+
vehicleModel.scene.position.set(-center.x, -center.y, -center.z);
|
|
582
|
+
tempGroup.add(vehicleModel.scene);
|
|
583
|
+
tempGroup.updateMatrixWorld(true);
|
|
584
|
+
let wheelRadius = 0, wheelWidth = 0, suspensionRestLength = 0, chassisHeight = 0, wheelSizeInit = false;
|
|
585
|
+
const wheelsInfo = [];
|
|
586
|
+
for (const wheel of wheelObjects) {
|
|
587
|
+
const worldPos = new THREE3.Vector3();
|
|
588
|
+
const worldQuat = new THREE3.Quaternion();
|
|
589
|
+
const worldScale = new THREE3.Vector3();
|
|
590
|
+
wheel.getWorldPosition(worldPos);
|
|
591
|
+
wheel.getWorldQuaternion(worldQuat);
|
|
592
|
+
wheel.getWorldScale(worldScale);
|
|
593
|
+
if (!wheelSizeInit) {
|
|
594
|
+
const { size: ws } = getBbox(wheel);
|
|
595
|
+
wheelRadius = Number((Math.max(ws.x, ws.y, ws.z) / 2).toFixed(2));
|
|
596
|
+
wheelWidth = Number(Math.min(ws.x, ws.y, ws.z).toFixed(2));
|
|
597
|
+
suspensionRestLength = Number((wheelRadius * 2 * suspensionRestLengthRatio).toFixed(2));
|
|
598
|
+
chassisHeight = Number((wheelRadius * 2 * chassisRatio).toFixed(2));
|
|
599
|
+
wheelSizeInit = true;
|
|
600
|
+
}
|
|
601
|
+
wheelsInfo.push({ axleCs: new THREE3.Vector3(0, 0, -1), position: worldPos, quaternion: worldQuat, scale: worldScale, radius: wheelRadius, width: wheelWidth, suspensionRestLength, object: wheel });
|
|
602
|
+
}
|
|
603
|
+
tempGroup.remove(vehicleModel.scene);
|
|
604
|
+
scene.remove(tempGroup);
|
|
605
|
+
const vehicleGroup = new THREE3.Group();
|
|
606
|
+
scene.add(vehicleGroup);
|
|
607
|
+
vehicleGroup.add(vehicleModel.scene);
|
|
608
|
+
vehicleGroup.updateMatrixWorld(true);
|
|
609
|
+
const wheelWrappers = [];
|
|
610
|
+
for (let i = 0; i < wheelsInfo.length; i++) {
|
|
611
|
+
const wheel = wheelsInfo[i];
|
|
612
|
+
const wheelWrapper = new THREE3.Group();
|
|
613
|
+
wheelWrapper.position.copy(vehicleGroup.worldToLocal(wheel.position.clone()));
|
|
614
|
+
const wheelObj = wheel.object;
|
|
615
|
+
wheelObj.parent?.remove(wheelObj);
|
|
616
|
+
wheelObj.position.set(0, 0, 0);
|
|
617
|
+
wheelObj.quaternion.copy(wheel.quaternion);
|
|
618
|
+
wheelObj.scale.copy(wheel.scale);
|
|
619
|
+
wheelObj.updateMatrixWorld();
|
|
620
|
+
wheelWrapper.add(wheelObj);
|
|
621
|
+
vehicleGroup.add(wheelWrapper);
|
|
622
|
+
wheelWrappers.push(wheelWrapper);
|
|
623
|
+
}
|
|
624
|
+
const halfExtents = size.clone().multiplyScalar(0.5);
|
|
625
|
+
halfExtents.y -= chassisHeight / 2;
|
|
626
|
+
vehicleModel.scene.position.y -= chassisHeight / 2;
|
|
627
|
+
halfExtents.x *= 0.95;
|
|
628
|
+
halfExtents.z *= 0.95;
|
|
629
|
+
const chassisBody = world.createRigidBody(
|
|
630
|
+
RAPIER.RigidBodyDesc.dynamic().setTranslation(opts.position.x, opts.position.y, opts.position.z).setLinearDamping(vehicleParams.chassis.linearDamping).setAngularDamping(vehicleParams.chassis.angularDamping).setCanSleep(true).setAdditionalMass(10)
|
|
631
|
+
);
|
|
632
|
+
world.createCollider(RAPIER.ColliderDesc.cuboid(halfExtents.x, halfExtents.y, halfExtents.z), chassisBody);
|
|
633
|
+
if (vehicleParams.debug.showPhysicsBox) {
|
|
634
|
+
vehicleGroup.add(new THREE3.Mesh(
|
|
635
|
+
new THREE3.BoxGeometry(halfExtents.x * 2, halfExtents.y * 2, halfExtents.z * 2),
|
|
636
|
+
new THREE3.MeshBasicMaterial({ color: 16711680, wireframe: true, transparent: true, opacity: 0.3 })
|
|
637
|
+
));
|
|
638
|
+
}
|
|
639
|
+
vehicleGroup.position.copy(opts.position);
|
|
640
|
+
vehicleGroup.updateMatrixWorld(true);
|
|
641
|
+
const { vehicle, updateWheelVisuals } = createVehicleController(world, chassisBody, wheelWrappers, wheelsInfo);
|
|
283
642
|
return {
|
|
284
|
-
|
|
643
|
+
vehicleGroup,
|
|
644
|
+
chassisBody,
|
|
645
|
+
vehicleController: vehicle,
|
|
285
646
|
updateWheelVisuals,
|
|
286
|
-
|
|
647
|
+
vehicleMixer,
|
|
648
|
+
vehicleActions,
|
|
649
|
+
vehiclIsOpenDoor: false,
|
|
650
|
+
vehicleBBox: bbox.clone(),
|
|
651
|
+
pathPlanner: new PathPlanner(
|
|
652
|
+
createObstacleChecker(vehicleGroup, bbox, scale, playerScale),
|
|
653
|
+
{ debugEnabled: false, scene, scale: playerScale }
|
|
654
|
+
),
|
|
655
|
+
scale,
|
|
656
|
+
boardingPoint: opts.boardingPoint,
|
|
657
|
+
seatOffset: opts.seatOffset ?? new THREE3.Vector3(),
|
|
658
|
+
enterVehicleTime: 1.5,
|
|
659
|
+
chassisRatio,
|
|
660
|
+
suspensionRestLengthRatio,
|
|
661
|
+
size: { l: Math.max(size.x, size.z), w: Math.min(size.x, size.z), h: size.y },
|
|
662
|
+
speedMultiplier
|
|
287
663
|
};
|
|
288
664
|
}
|
|
289
665
|
|
|
290
666
|
// src/playerController.ts
|
|
291
|
-
|
|
667
|
+
THREE4.Mesh.prototype.raycast = acceleratedRaycast;
|
|
292
668
|
var controllerInstance = null;
|
|
293
|
-
var clock = new
|
|
669
|
+
var clock = new THREE4.Clock();
|
|
670
|
+
function isMobileDevice() {
|
|
671
|
+
return /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent);
|
|
672
|
+
}
|
|
294
673
|
var PlayerController = class {
|
|
295
674
|
constructor() {
|
|
296
|
-
// ====================
|
|
675
|
+
// ==================== 场景引用 ====================
|
|
297
676
|
this.loader = new GLTFLoader();
|
|
298
|
-
|
|
299
|
-
|
|
677
|
+
// 物理参数
|
|
678
|
+
this.gravity = -2400;
|
|
679
|
+
this.jumpHeight = 600;
|
|
680
|
+
this.playerSpeed = 300;
|
|
681
|
+
this.playerFlySpeed = 2100;
|
|
682
|
+
this.curPlayerSpeed = 0;
|
|
683
|
+
// 交互参数
|
|
684
|
+
this.mouseSensitivity = 5;
|
|
685
|
+
this.thirdMouseMode = 1;
|
|
686
|
+
this.enableZoom = false;
|
|
687
|
+
this.playerFlyEnabled = true;
|
|
688
|
+
this.isShowMobileControls = true;
|
|
300
689
|
this.enableOverShoulderView = false;
|
|
301
|
-
|
|
302
|
-
// ==================== 玩家基本属性 ====================
|
|
690
|
+
// ==================== 玩家胶囊体 ====================
|
|
303
691
|
this.playerCapsuleRadius = 45;
|
|
304
|
-
this.playerCapsuleRadiusRatio = 1;
|
|
305
692
|
this.playerCapsuleHeight = 180;
|
|
306
|
-
|
|
693
|
+
this.playerCapsuleRadiusRatio = 1;
|
|
307
694
|
this.isFirstPerson = false;
|
|
308
695
|
this.boundingBoxMinY = 0;
|
|
309
|
-
// ====================
|
|
310
|
-
this.
|
|
311
|
-
|
|
312
|
-
this.
|
|
313
|
-
|
|
696
|
+
// ==================== 运行状态 ====================
|
|
697
|
+
this.controllerMode = 0;
|
|
698
|
+
// 0:人物 1:车辆
|
|
699
|
+
this.playerIsOnGround = false;
|
|
700
|
+
this.isupdate = true;
|
|
701
|
+
this.isFlying = false;
|
|
702
|
+
this.isChangeControllerTransitionTimer = null;
|
|
703
|
+
// ==================== 输入按键 ====================
|
|
704
|
+
this.fwdPressed = false;
|
|
705
|
+
this.bkdPressed = false;
|
|
706
|
+
this.lftPressed = false;
|
|
707
|
+
this.rgtPressed = false;
|
|
708
|
+
this.spacePressed = false;
|
|
709
|
+
this.ctPressed = false;
|
|
710
|
+
this.shiftPressed = false;
|
|
711
|
+
// ==================== 相机参数 ====================
|
|
712
|
+
this.camCollisionLerp = 0.18;
|
|
713
|
+
this.camEpsilon = 35;
|
|
714
|
+
this.minCamDistance = 100;
|
|
715
|
+
this.maxCamDistance = 440;
|
|
716
|
+
this.orginMaxCamDistance = 440;
|
|
717
|
+
// ==================== 场景碰撞体 ====================
|
|
314
718
|
this.collider = null;
|
|
315
719
|
this.visualizer = null;
|
|
316
720
|
this.person = null;
|
|
@@ -318,150 +722,78 @@ var PlayerController = class {
|
|
|
318
722
|
this.collected = [];
|
|
319
723
|
this.dynamicCollider = null;
|
|
320
724
|
this.dynamicCollected = [];
|
|
321
|
-
// ====================
|
|
725
|
+
// ==================== 车辆系统 ====================
|
|
322
726
|
this.vehicles = [];
|
|
323
|
-
// 所有已加载车辆
|
|
324
727
|
this.activeVehicle = null;
|
|
325
|
-
// 当前驾驶/交互的车辆
|
|
326
728
|
this.vehicleLength = 6;
|
|
327
|
-
// 车辆参考长度
|
|
328
|
-
this.wheelSteeringQuat = new THREE3.Quaternion();
|
|
329
|
-
this.wheelRotationQuat = new THREE3.Quaternion();
|
|
330
729
|
this.RAPIER = null;
|
|
331
730
|
this.world = null;
|
|
332
|
-
|
|
731
|
+
this.wheelSteeringQuat = new THREE4.Quaternion();
|
|
732
|
+
this.wheelRotationQuat = new THREE4.Quaternion();
|
|
733
|
+
this.camBehindDir = new THREE4.Vector3(0, 0, 1);
|
|
333
734
|
this.vehicleParams = {
|
|
334
|
-
debug: {
|
|
335
|
-
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
angularDamping: 0.5
|
|
340
|
-
},
|
|
341
|
-
model: {
|
|
342
|
-
rotation: -Math.PI / 2
|
|
343
|
-
},
|
|
344
|
-
power: {
|
|
345
|
-
accelerateForce: 50,
|
|
346
|
-
// 推进
|
|
347
|
-
brakeForce: 200,
|
|
348
|
-
// 刹车
|
|
349
|
-
maxSpeed: 1e4
|
|
350
|
-
// 最大速度
|
|
351
|
-
},
|
|
352
|
-
steering: {
|
|
353
|
-
maxSteerAngle: Math.PI / 4,
|
|
354
|
-
steerSpeed: 0.5,
|
|
355
|
-
steerReturnSpeed: 1
|
|
356
|
-
},
|
|
735
|
+
debug: { showPhysicsBox: false },
|
|
736
|
+
chassis: { linearDamping: 0.5, angularDamping: 0.5 },
|
|
737
|
+
model: { rotation: -Math.PI / 2 },
|
|
738
|
+
power: { accelerateForce: 50, brakeForce: 200, maxSpeed: 1e4 },
|
|
739
|
+
steering: { maxSteerAngle: Math.PI / 4, steerSpeed: 0.5, steerReturnSpeed: 1 },
|
|
357
740
|
followVehicleDirection: true
|
|
358
741
|
};
|
|
359
|
-
|
|
360
|
-
// ==================== 上车相关 ====================
|
|
742
|
+
// ==================== 上车流程 ====================
|
|
361
743
|
this.isMovingToBoardingPoint = false;
|
|
362
744
|
this.boardingWaypoints = [];
|
|
363
745
|
this.currentWaypointIndex = 0;
|
|
364
746
|
this.boardingTargetDir = null;
|
|
365
747
|
this.boardingMoveSpeed = 300;
|
|
366
748
|
this.boardingRotateSpeed = 10;
|
|
367
|
-
this.flip180Quat = new THREE3.Quaternion().setFromAxisAngle(
|
|
368
|
-
new THREE3.Vector3(0, 1, 0),
|
|
369
|
-
Math.PI
|
|
370
|
-
);
|
|
371
|
-
this.closeVehicleDoorTimer = null;
|
|
372
749
|
this.boardingPointWorld = null;
|
|
373
750
|
this.isBoardingAnimPlaying = false;
|
|
374
751
|
this.closeDoorTriggered = false;
|
|
375
752
|
this.isExitAnimPlaying = false;
|
|
376
753
|
this.closeExitDoorTriggered = false;
|
|
377
|
-
|
|
378
|
-
this.
|
|
379
|
-
this.isupdate = true;
|
|
380
|
-
this.isFlying = false;
|
|
381
|
-
// ==================== 输入状态 ====================
|
|
382
|
-
this.fwdPressed = false;
|
|
383
|
-
this.bkdPressed = false;
|
|
384
|
-
this.lftPressed = false;
|
|
385
|
-
this.rgtPressed = false;
|
|
386
|
-
this.spacePressed = false;
|
|
387
|
-
this.ctPressed = false;
|
|
388
|
-
this.shiftPressed = false;
|
|
389
|
-
// ==================== 移动端输入 ====================
|
|
390
|
-
this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
|
|
391
|
-
this.nippleModule = null;
|
|
392
|
-
this.joystickManager = null;
|
|
393
|
-
this.joystickZoneEl = null;
|
|
394
|
-
this.lookAreaEl = null;
|
|
395
|
-
this.jumpBtnEl = null;
|
|
396
|
-
this.flyBtnEl = null;
|
|
397
|
-
this.viewBtnEl = null;
|
|
398
|
-
this.vehicleBtnEl = null;
|
|
399
|
-
this.lookPointerId = null;
|
|
400
|
-
this.isLookDown = false;
|
|
401
|
-
this.lastTouchX = 0;
|
|
402
|
-
this.lastTouchY = 0;
|
|
403
|
-
this.nearCheckLocal = new THREE3.Vector3();
|
|
404
|
-
this.nearCheckWorld = new THREE3.Vector3();
|
|
405
|
-
this.isNearVehicle = false;
|
|
406
|
-
// ==================== 第三人称相机参数 ====================
|
|
407
|
-
this._camCollisionLerp = 0.18;
|
|
408
|
-
this._camEpsilon = 0.35;
|
|
409
|
-
this.minCamDistance = 1;
|
|
410
|
-
this.maxCamDistance = 4.4;
|
|
411
|
-
this.orginMaxCamDistance = 4.4;
|
|
412
|
-
// ==================== 物理/运动 ====================
|
|
413
|
-
this.playerVelocity = new THREE3.Vector3();
|
|
414
|
-
this.upVector = new THREE3.Vector3(0, 1, 0);
|
|
415
|
-
// ==================== 临时复用向量/矩阵 ====================
|
|
416
|
-
this.tempVector = new THREE3.Vector3();
|
|
417
|
-
this.tempVector2 = new THREE3.Vector3();
|
|
418
|
-
this.tempBox = new THREE3.Box3();
|
|
419
|
-
this.tempMat = new THREE3.Matrix4();
|
|
420
|
-
this.tempSegment = new THREE3.Line3();
|
|
754
|
+
this.closeVehicleDoorTimer = null;
|
|
755
|
+
this.flip180Quat = new THREE4.Quaternion().setFromAxisAngle(new THREE4.Vector3(0, 1, 0), Math.PI);
|
|
421
756
|
this.recheckAnimTimer = null;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
this.
|
|
426
|
-
this.
|
|
757
|
+
this.allAnimations = [];
|
|
758
|
+
// ==================== 移动端控制 ====================
|
|
759
|
+
this.mobileControls = null;
|
|
760
|
+
this.nearCheckLocal = new THREE4.Vector3();
|
|
761
|
+
this.nearCheckWorld = new THREE4.Vector3();
|
|
762
|
+
this.isNearVehicle = false;
|
|
763
|
+
// ==================== 调试显示 ====================
|
|
764
|
+
this.displayPlayer = false;
|
|
765
|
+
this.displayCollider = false;
|
|
766
|
+
this.displayVisualizer = false;
|
|
767
|
+
// ==================== 方向常量 ====================
|
|
427
768
|
this.rotationSpeed = 10;
|
|
428
|
-
this.DIR_FWD = new
|
|
429
|
-
this.DIR_BKD = new
|
|
430
|
-
this.DIR_LFT = new
|
|
431
|
-
this.DIR_RGT = new
|
|
432
|
-
this.DIR_UP = new
|
|
769
|
+
this.DIR_FWD = new THREE4.Vector3(0, 0, -1);
|
|
770
|
+
this.DIR_BKD = new THREE4.Vector3(0, 0, 1);
|
|
771
|
+
this.DIR_LFT = new THREE4.Vector3(-1, 0, 0);
|
|
772
|
+
this.DIR_RGT = new THREE4.Vector3(1, 0, 0);
|
|
773
|
+
this.DIR_UP = new THREE4.Vector3(0, 1, 0);
|
|
774
|
+
// ==================== 复用向量 ====================
|
|
775
|
+
this.upVector = new THREE4.Vector3(0, 1, 0);
|
|
776
|
+
this.playerVelocity = new THREE4.Vector3();
|
|
777
|
+
this.camDir = new THREE4.Vector3();
|
|
778
|
+
this.moveDir = new THREE4.Vector3();
|
|
779
|
+
this.targetQuat = new THREE4.Quaternion();
|
|
780
|
+
this.targetMat = new THREE4.Matrix4();
|
|
781
|
+
this.tempVector = new THREE4.Vector3();
|
|
782
|
+
this.tempVector2 = new THREE4.Vector3();
|
|
783
|
+
this.tempBox = new THREE4.Box3();
|
|
784
|
+
this.tempMat = new THREE4.Matrix4();
|
|
785
|
+
this.tempSegment = new THREE4.Line3();
|
|
433
786
|
// ==================== 射线检测 ====================
|
|
434
|
-
this.
|
|
435
|
-
this.
|
|
436
|
-
this.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
new THREE3.Vector3(),
|
|
442
|
-
new THREE3.Vector3()
|
|
443
|
-
);
|
|
444
|
-
this.centerRay = new THREE3.Raycaster();
|
|
445
|
-
this.centerMouse = new THREE3.Vector2();
|
|
446
|
-
// ==================== 物理与碰撞检测 ====================
|
|
447
|
-
this.ensureAttributesMinimal = (geom) => {
|
|
448
|
-
if (!geom.attributes.position) return null;
|
|
449
|
-
if (!geom.attributes.normal) geom.computeVertexNormals();
|
|
450
|
-
if (!geom.attributes.uv) {
|
|
451
|
-
const count = geom.attributes.position.count;
|
|
452
|
-
const dummyUV = new Float32Array(count * 2);
|
|
453
|
-
geom.setAttribute("uv", new THREE3.BufferAttribute(dummyUV, 2));
|
|
454
|
-
}
|
|
455
|
-
return geom;
|
|
456
|
-
};
|
|
787
|
+
this.personToCam = new THREE4.Vector3();
|
|
788
|
+
this.originTmp = new THREE4.Vector3();
|
|
789
|
+
this.raycaster = new THREE4.Raycaster(new THREE4.Vector3(), new THREE4.Vector3(0, -1, 0));
|
|
790
|
+
this.raycasterPersonToCam = new THREE4.Raycaster(new THREE4.Vector3(), new THREE4.Vector3());
|
|
791
|
+
this.centerRay = new THREE4.Raycaster();
|
|
792
|
+
this.centerMouse = new THREE4.Vector2();
|
|
793
|
+
// 按键动画同步
|
|
457
794
|
this.setAnimationByPressed = () => {
|
|
458
795
|
this.maxCamDistance = this.orginMaxCamDistance;
|
|
459
|
-
|
|
460
|
-
this.isMovingToBoardingPoint = false;
|
|
461
|
-
this.boardingWaypoints = [];
|
|
462
|
-
this.currentWaypointIndex = 0;
|
|
463
|
-
this.boardingTargetDir = null;
|
|
464
|
-
}
|
|
796
|
+
this.cancelBoarding();
|
|
465
797
|
if (this.isExitAnimPlaying) {
|
|
466
798
|
this.isExitAnimPlaying = false;
|
|
467
799
|
this.closeExitDoorTriggered = false;
|
|
@@ -489,15 +821,11 @@ var PlayerController = class {
|
|
|
489
821
|
return;
|
|
490
822
|
}
|
|
491
823
|
if (this.fwdPressed) {
|
|
492
|
-
this.playPersonAnimationByName(
|
|
493
|
-
this.shiftPressed ? "running" : "walking"
|
|
494
|
-
);
|
|
824
|
+
this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
|
|
495
825
|
return;
|
|
496
826
|
}
|
|
497
827
|
if (!this.isFirstPerson && (this.lftPressed || this.rgtPressed || this.bkdPressed)) {
|
|
498
|
-
this.playPersonAnimationByName(
|
|
499
|
-
this.shiftPressed ? "running" : "walking"
|
|
500
|
-
);
|
|
828
|
+
this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
|
|
501
829
|
return;
|
|
502
830
|
}
|
|
503
831
|
if (this.lftPressed) {
|
|
@@ -513,19 +841,16 @@ var PlayerController = class {
|
|
|
513
841
|
return;
|
|
514
842
|
}
|
|
515
843
|
}
|
|
516
|
-
if (this.recheckAnimTimer !== null)
|
|
517
|
-
clearTimeout(this.recheckAnimTimer);
|
|
518
|
-
}
|
|
844
|
+
if (this.recheckAnimTimer !== null) clearTimeout(this.recheckAnimTimer);
|
|
519
845
|
this.recheckAnimTimer = setTimeout(() => {
|
|
520
846
|
this.setAnimationByPressed();
|
|
521
847
|
this.recheckAnimTimer = null;
|
|
522
848
|
}, 200);
|
|
523
849
|
};
|
|
524
850
|
// ==================== 事件处理 ====================
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
851
|
+
// 键盘按下事件
|
|
852
|
+
this.boundOnKeydown = async (e) => {
|
|
853
|
+
if (e.ctrlKey && ["KeyW", "KeyA", "KeyS", "KeyD"].includes(e.code)) e.preventDefault();
|
|
529
854
|
switch (e.code) {
|
|
530
855
|
case "KeyW":
|
|
531
856
|
case "ArrowUp":
|
|
@@ -554,17 +879,11 @@ var PlayerController = class {
|
|
|
554
879
|
this.controls.mouseButtons = { LEFT: 2, MIDDLE: 1, RIGHT: 0 };
|
|
555
880
|
break;
|
|
556
881
|
case "Space":
|
|
557
|
-
|
|
558
|
-
this.isMovingToBoardingPoint = false;
|
|
559
|
-
this.boardingWaypoints = [];
|
|
560
|
-
this.currentWaypointIndex = 0;
|
|
561
|
-
this.boardingTargetDir = null;
|
|
562
|
-
}
|
|
882
|
+
this.cancelBoarding();
|
|
563
883
|
this.spacePressed = true;
|
|
564
|
-
if (this.controllerMode
|
|
884
|
+
if (this.controllerMode === 1) return;
|
|
565
885
|
if (!this.playerIsOnGround || this.isFlying) return;
|
|
566
|
-
|
|
567
|
-
if (next && this.actionState === next) return;
|
|
886
|
+
if (this.personActions?.get("jumping") === this.actionState) return;
|
|
568
887
|
this.playPersonAnimationByName("jumping");
|
|
569
888
|
this.playerVelocity.y = this.jumpHeight;
|
|
570
889
|
this.playerIsOnGround = false;
|
|
@@ -576,25 +895,21 @@ var PlayerController = class {
|
|
|
576
895
|
this.changeView();
|
|
577
896
|
break;
|
|
578
897
|
case "KeyF":
|
|
579
|
-
if (this.controllerMode
|
|
898
|
+
if (this.controllerMode === 0 && this.playerFlyEnabled) {
|
|
580
899
|
this.isFlying = !this.isFlying;
|
|
581
900
|
this.setAnimationByPressed();
|
|
582
|
-
if (!this.isFlying && !this.playerIsOnGround)
|
|
583
|
-
this.playPersonAnimationByName("jumping");
|
|
584
|
-
}
|
|
901
|
+
if (!this.isFlying && !this.playerIsOnGround) this.playPersonAnimationByName("jumping");
|
|
585
902
|
}
|
|
586
903
|
break;
|
|
587
904
|
case "KeyE":
|
|
588
905
|
if (this.isFlying) return;
|
|
589
|
-
if (this.controllerMode
|
|
590
|
-
|
|
591
|
-
} else {
|
|
592
|
-
this.exitVehicle();
|
|
593
|
-
}
|
|
906
|
+
if (this.controllerMode === 0) this.enterVehicle();
|
|
907
|
+
else this.exitVehicle();
|
|
594
908
|
break;
|
|
595
909
|
}
|
|
596
910
|
};
|
|
597
|
-
|
|
911
|
+
// 键盘抬起事件
|
|
912
|
+
this.boundOnKeyup = (e) => {
|
|
598
913
|
switch (e.code) {
|
|
599
914
|
case "KeyW":
|
|
600
915
|
case "ArrowUp":
|
|
@@ -630,192 +945,112 @@ var PlayerController = class {
|
|
|
630
945
|
break;
|
|
631
946
|
}
|
|
632
947
|
};
|
|
633
|
-
this.
|
|
634
|
-
if (document.pointerLockElement
|
|
635
|
-
this.setToward(e.movementX, e.movementY, 1e-4);
|
|
636
|
-
};
|
|
637
|
-
this._mouseClick = (_e) => {
|
|
638
|
-
this.setPointerLock();
|
|
639
|
-
};
|
|
640
|
-
// ==================== 移动端控制 ====================
|
|
641
|
-
this.onPointerDown = (e) => {
|
|
642
|
-
if (e.pointerType !== "touch") return;
|
|
643
|
-
this.isLookDown = true;
|
|
644
|
-
this.lookPointerId = e.pointerId;
|
|
645
|
-
this.lastTouchX = e.clientX;
|
|
646
|
-
this.lastTouchY = e.clientY;
|
|
647
|
-
this.lookAreaEl?.setPointerCapture?.(e.pointerId);
|
|
648
|
-
e.preventDefault();
|
|
649
|
-
};
|
|
650
|
-
this.onPointerMove = (e) => {
|
|
651
|
-
if (!this.isLookDown || e.pointerId !== this.lookPointerId) return;
|
|
652
|
-
const dx = e.clientX - this.lastTouchX;
|
|
653
|
-
const dy = e.clientY - this.lastTouchY;
|
|
654
|
-
this.lastTouchX = e.clientX;
|
|
655
|
-
this.lastTouchY = e.clientY;
|
|
656
|
-
this.setInput({ lookDeltaX: dx, lookDeltaY: dy });
|
|
657
|
-
e.preventDefault();
|
|
658
|
-
};
|
|
659
|
-
this.onPointerUp = (e) => {
|
|
660
|
-
if (e.pointerId !== this.lookPointerId) return;
|
|
661
|
-
this.isLookDown = false;
|
|
662
|
-
this.lookPointerId = null;
|
|
663
|
-
this.lookAreaEl?.releasePointerCapture?.(e.pointerId);
|
|
948
|
+
this.mouseMove = (e) => {
|
|
949
|
+
if (document.pointerLockElement === document.body) this.setToward(e.movementX, e.movementY, 1e-4);
|
|
664
950
|
};
|
|
665
|
-
this.
|
|
666
|
-
this.
|
|
951
|
+
this.mouseClick = () => this.setPointerLock();
|
|
952
|
+
this.raycaster.firstHitOnly = true;
|
|
953
|
+
this.raycasterPersonToCam.firstHitOnly = true;
|
|
667
954
|
}
|
|
668
|
-
// ====================
|
|
955
|
+
// ==================== 初始化 ====================
|
|
956
|
+
// 初始化控制器
|
|
669
957
|
async init(opts, callback) {
|
|
958
|
+
const m = opts.playerModel;
|
|
959
|
+
const s = m.scale ?? 1;
|
|
670
960
|
this.scene = opts.scene;
|
|
671
961
|
this.camera = opts.camera;
|
|
672
962
|
this.camera.rotation.order = "YXZ";
|
|
673
963
|
this.controls = opts.controls;
|
|
674
|
-
this.playerModel =
|
|
675
|
-
this.initPos = opts.initPos
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
this.
|
|
679
|
-
this.
|
|
680
|
-
this.
|
|
681
|
-
this.playerFlySpeed = (opts.playerModel.flySpeed ?? 2100) * s;
|
|
964
|
+
this.playerModel = m;
|
|
965
|
+
this.initPos = opts.initPos ? new THREE4.Vector3(opts.initPos.x, opts.initPos.y, opts.initPos.z) : new THREE4.Vector3(0, 0, 0);
|
|
966
|
+
const pm = this.playerModel;
|
|
967
|
+
this.gravity = (pm.gravity ?? this.gravity) * s;
|
|
968
|
+
this.jumpHeight = (pm.jumpHeight ?? this.jumpHeight) * s;
|
|
969
|
+
this.playerSpeed = (pm.speed ?? this.playerSpeed) * s;
|
|
970
|
+
this.playerFlySpeed = (pm.flySpeed ?? this.playerFlySpeed) * s;
|
|
682
971
|
this.curPlayerSpeed = this.playerSpeed;
|
|
683
|
-
this.
|
|
684
|
-
this.
|
|
685
|
-
this.
|
|
686
|
-
this.
|
|
687
|
-
this.
|
|
688
|
-
this.
|
|
972
|
+
this.playerFlyEnabled = pm.flyEnabled ?? this.playerFlyEnabled;
|
|
973
|
+
this.playerCapsuleRadiusRatio = pm.capsuleRadiusRatio ?? this.playerCapsuleRadiusRatio;
|
|
974
|
+
this.mouseSensitivity = opts.mouseSensitivity ?? this.mouseSensitivity;
|
|
975
|
+
this.thirdMouseMode = opts.thirdMouseMode ?? this.thirdMouseMode;
|
|
976
|
+
this.enableZoom = opts.enableZoom ?? this.enableZoom;
|
|
977
|
+
this.minCamDistance = (opts.minCamDistance ?? this.minCamDistance) * s;
|
|
978
|
+
this.maxCamDistance = (opts.maxCamDistance ?? this.maxCamDistance) * s;
|
|
689
979
|
this.orginMaxCamDistance = this.maxCamDistance;
|
|
690
|
-
this.
|
|
691
|
-
this.
|
|
692
|
-
this.
|
|
693
|
-
const isMobileDevice = () => navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
694
|
-
this.isShowMobileControls = (opts.isShowMobileControls ?? true) && isMobileDevice();
|
|
980
|
+
this.camEpsilon = this.camEpsilon * s;
|
|
981
|
+
this.isShowMobileControls = (opts.isShowMobileControls ?? this.isShowMobileControls) && isMobileDevice();
|
|
982
|
+
this.enableOverShoulderView = opts.enableOverShoulderView ?? this.enableOverShoulderView;
|
|
695
983
|
if (this.isShowMobileControls) {
|
|
696
|
-
|
|
984
|
+
this.mobileControls = new MobileControls((i) => this.setInput(i), this.controls);
|
|
985
|
+
await this.mobileControls.init();
|
|
697
986
|
}
|
|
698
987
|
await this.createBVH(opts.colliderMeshUrl);
|
|
699
988
|
await this.loadPersonGLB();
|
|
700
989
|
this.onAllEvent();
|
|
701
990
|
this.setCameraPos();
|
|
702
991
|
this.setControls();
|
|
703
|
-
if (callback) callback();
|
|
704
|
-
this.enableOverShoulderView = opts.enableOverShoulderView ?? false;
|
|
705
992
|
this.setOverShoulderView(this.enableOverShoulderView);
|
|
993
|
+
callback?.();
|
|
706
994
|
}
|
|
995
|
+
// 过肩视角切换
|
|
707
996
|
setOverShoulderView(enable) {
|
|
708
|
-
if (!enable || this.controllerMode
|
|
997
|
+
if (!enable || this.controllerMode === 1) {
|
|
709
998
|
this.camera.clearViewOffset();
|
|
710
999
|
return;
|
|
711
1000
|
}
|
|
712
1001
|
const w = window.innerWidth;
|
|
713
1002
|
const h = window.innerHeight;
|
|
714
|
-
this.camera.setViewOffset(
|
|
715
|
-
w,
|
|
716
|
-
h,
|
|
717
|
-
-w * -0.15,
|
|
718
|
-
0,
|
|
719
|
-
w,
|
|
720
|
-
h
|
|
721
|
-
);
|
|
1003
|
+
this.camera.setViewOffset(w, h, -w * -0.15, 0, w, h);
|
|
722
1004
|
}
|
|
1005
|
+
// 初始化加载器
|
|
723
1006
|
async initLoader() {
|
|
724
1007
|
const dracoLoader = new DRACOLoader();
|
|
725
1008
|
dracoLoader.setDecoderPath("https://unpkg.com/three@0.180.0/examples/jsm/libs/draco/gltf/");
|
|
726
1009
|
dracoLoader.setDecoderConfig({ type: "js" });
|
|
727
1010
|
this.loader.setDRACOLoader(dracoLoader);
|
|
728
1011
|
}
|
|
1012
|
+
// 初始化物理引擎
|
|
729
1013
|
async initRapier() {
|
|
730
1014
|
if (this.RAPIER) return;
|
|
731
1015
|
this.RAPIER = await import("@dimforge/rapier3d-compat");
|
|
732
1016
|
await this.RAPIER.init();
|
|
733
|
-
|
|
734
|
-
this.world = new this.RAPIER.World(gravity);
|
|
1017
|
+
this.world = new this.RAPIER.World(new this.RAPIER.Vector3(0, -9.81, 0));
|
|
735
1018
|
this.world.maxCcdSubsteps = 2;
|
|
736
|
-
const
|
|
737
|
-
let
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
tmp.
|
|
747
|
-
vertices[i * 3 + 0] = tmp.x;
|
|
748
|
-
vertices[i * 3 + 1] = tmp.y;
|
|
749
|
-
vertices[i * 3 + 2] = tmp.z;
|
|
1019
|
+
const addTrimesh = (RAPIER, world, geom) => {
|
|
1020
|
+
let g = geom.index ? geom.clone().toNonIndexed() : geom.clone();
|
|
1021
|
+
const pos = g.attributes.position;
|
|
1022
|
+
const count = pos.count;
|
|
1023
|
+
const verts = new Float32Array(count * 3);
|
|
1024
|
+
const tmp = new THREE4.Vector3();
|
|
1025
|
+
for (let i = 0; i < count; i++) {
|
|
1026
|
+
tmp.fromBufferAttribute(pos, i);
|
|
1027
|
+
verts[i * 3] = tmp.x;
|
|
1028
|
+
verts[i * 3 + 1] = tmp.y;
|
|
1029
|
+
verts[i * 3 + 2] = tmp.z;
|
|
750
1030
|
}
|
|
751
|
-
const indices =
|
|
752
|
-
for (let i = 0; i <
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1031
|
+
const indices = count > 65535 ? new Uint32Array(count) : new Uint16Array(count);
|
|
1032
|
+
for (let i = 0; i < count; i++) indices[i] = i;
|
|
1033
|
+
const body = world.createRigidBody(RAPIER.RigidBodyDesc.fixed());
|
|
1034
|
+
world.createCollider(
|
|
1035
|
+
RAPIER.ColliderDesc.trimesh(verts, indices).setRestitution(0).setFriction(0.8),
|
|
1036
|
+
body
|
|
1037
|
+
);
|
|
757
1038
|
};
|
|
758
|
-
for (const g of this.collected)
|
|
759
|
-
|
|
760
|
-
}
|
|
761
|
-
const groundDesc = this.RAPIER.RigidBodyDesc.fixed();
|
|
762
|
-
const groundBody = this.world.createRigidBody(groundDesc);
|
|
1039
|
+
for (const g of this.collected) addTrimesh(this.RAPIER, this.world, g);
|
|
1040
|
+
const groundBody = this.world.createRigidBody(this.RAPIER.RigidBodyDesc.fixed());
|
|
763
1041
|
groundBody.userData = { outOfBounds: true };
|
|
764
1042
|
}
|
|
765
|
-
// ====================
|
|
1043
|
+
// ==================== 玩家模型 ====================
|
|
1044
|
+
// 加载玩家模型
|
|
766
1045
|
async loadPersonGLB() {
|
|
767
1046
|
try {
|
|
768
|
-
const gltf = await this.loader.loadAsync(
|
|
769
|
-
this.playerModel.url
|
|
770
|
-
);
|
|
1047
|
+
const gltf = await this.loader.loadAsync(this.playerModel.url);
|
|
771
1048
|
this.person = gltf.scene;
|
|
772
|
-
|
|
773
|
-
const ratio = this.playerCapsuleHeight / size.y;
|
|
774
|
-
const modelScale = ratio;
|
|
775
|
-
this.playerCapsuleRadius = Number(Math.min(size.x, size.z).toFixed(0)) * modelScale * this.playerCapsuleRadiusRatio;
|
|
776
|
-
this.playerCapsuleHeight = Number(size.y.toFixed(0)) * modelScale;
|
|
777
|
-
const scale = this.playerModel.scale;
|
|
778
|
-
const material = new THREE3.MeshStandardMaterial({
|
|
779
|
-
color: new THREE3.Color(1, 0, 0),
|
|
780
|
-
shadowSide: THREE3.DoubleSide,
|
|
781
|
-
depthTest: false,
|
|
782
|
-
transparent: true,
|
|
783
|
-
opacity: this.displayPlayer ? 0.5 : 0,
|
|
784
|
-
wireframe: true,
|
|
785
|
-
depthWrite: false
|
|
786
|
-
});
|
|
787
|
-
const r = this.playerCapsuleRadius * scale;
|
|
788
|
-
const h = this.playerCapsuleHeight * scale;
|
|
789
|
-
this.player = new THREE3.Mesh(
|
|
790
|
-
new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75),
|
|
791
|
-
material
|
|
792
|
-
);
|
|
793
|
-
this.player.geometry.translate(0, -h * 0.25, 0);
|
|
794
|
-
this.player.capsuleInfo = {
|
|
795
|
-
radius: r,
|
|
796
|
-
segment: new THREE3.Line3(
|
|
797
|
-
new THREE3.Vector3(),
|
|
798
|
-
new THREE3.Vector3(0, -h * 0.5, 0)
|
|
799
|
-
)
|
|
800
|
-
};
|
|
801
|
-
this.player.name = "capsule";
|
|
802
|
-
this.scene.add(this.player);
|
|
803
|
-
this.reset();
|
|
804
|
-
this.player.rotateY(this.playerModel.rotateY ?? 0);
|
|
805
|
-
this.person.scale.multiplyScalar(modelScale * scale);
|
|
806
|
-
this.person.position.set(0, -h * 0.75, 0);
|
|
807
|
-
this.person.traverse((child) => {
|
|
808
|
-
if (child.name == this.playerModel?.headObjName) {
|
|
809
|
-
this.personHead = child;
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
this.player.add(this.person);
|
|
813
|
-
this.reset();
|
|
814
|
-
this.personMixer = new THREE3.AnimationMixer(this.person);
|
|
1049
|
+
this.personMixer = new THREE4.AnimationMixer(this.person);
|
|
815
1050
|
const animations = gltf.animations ?? [];
|
|
816
|
-
|
|
1051
|
+
this.allAnimations = animations;
|
|
817
1052
|
this.personActions = /* @__PURE__ */ new Map();
|
|
818
|
-
const
|
|
1053
|
+
const mappings = [
|
|
819
1054
|
[this.playerModel.idleAnim, "idle"],
|
|
820
1055
|
[this.playerModel.walkAnim, "walking"],
|
|
821
1056
|
[this.playerModel.leftWalkAnim || this.playerModel.walkAnim, "left_walking"],
|
|
@@ -828,43 +1063,30 @@ var PlayerController = class {
|
|
|
828
1063
|
[this.playerModel.enterCarAnim || this.playerModel.idleAnim, "enterCar"],
|
|
829
1064
|
[this.playerModel.exitCarAnim || this.playerModel.idleAnim, "exitCar"]
|
|
830
1065
|
];
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
const clip = findClip(clipName);
|
|
1066
|
+
for (const [clipName, actionName] of mappings) {
|
|
1067
|
+
const clip = animations.find((a) => a.name === clipName);
|
|
834
1068
|
if (!clip) continue;
|
|
835
1069
|
const action = this.personMixer.clipAction(clip);
|
|
836
1070
|
if (actionName === "jumping") {
|
|
837
|
-
action.setLoop(
|
|
1071
|
+
action.setLoop(THREE4.LoopOnce, 1);
|
|
838
1072
|
action.clampWhenFinished = true;
|
|
839
1073
|
action.setEffectiveTimeScale(1.2);
|
|
840
1074
|
} else {
|
|
841
|
-
action.setLoop(
|
|
842
|
-
action.clampWhenFinished = false;
|
|
1075
|
+
action.setLoop(THREE4.LoopRepeat, Infinity);
|
|
843
1076
|
action.setEffectiveTimeScale(1);
|
|
844
1077
|
}
|
|
845
1078
|
action.enabled = true;
|
|
846
1079
|
action.setEffectiveWeight(0);
|
|
847
1080
|
this.personActions.set(actionName, action);
|
|
848
1081
|
}
|
|
849
|
-
this.
|
|
850
|
-
this.
|
|
851
|
-
this.
|
|
852
|
-
this.rightWalkAction = this.personActions.get("right_walking");
|
|
853
|
-
this.backwardAction = this.personActions.get("walking_backward");
|
|
854
|
-
this.jumpAction = this.personActions.get("jumping");
|
|
855
|
-
this.runAction = this.personActions.get("running");
|
|
856
|
-
this.flyidleAction = this.personActions.get("flyidle");
|
|
857
|
-
this.flyAction = this.personActions.get("flying");
|
|
858
|
-
this.idleAction.setEffectiveWeight(1);
|
|
859
|
-
this.idleAction.play();
|
|
860
|
-
this.actionState = this.idleAction;
|
|
1082
|
+
this.personActions.get("idle").setEffectiveWeight(1);
|
|
1083
|
+
this.personActions.get("idle").play();
|
|
1084
|
+
this.actionState = this.personActions.get("idle");
|
|
861
1085
|
this.personMixer.addEventListener("finished", (ev) => {
|
|
862
|
-
const
|
|
863
|
-
if (
|
|
1086
|
+
const done = ev.action;
|
|
1087
|
+
if (done === this.personActions.get("jumping")) {
|
|
864
1088
|
if (this.fwdPressed) {
|
|
865
|
-
this.playPersonAnimationByName(
|
|
866
|
-
this.shiftPressed ? "running" : "walking"
|
|
867
|
-
);
|
|
1089
|
+
this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
|
|
868
1090
|
return;
|
|
869
1091
|
}
|
|
870
1092
|
if (this.bkdPressed) {
|
|
@@ -877,26 +1099,56 @@ var PlayerController = class {
|
|
|
877
1099
|
}
|
|
878
1100
|
this.playPersonAnimationByName("idle");
|
|
879
1101
|
}
|
|
880
|
-
if (
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1102
|
+
if (done === this.personActions?.get("enterCar")) this.onEnterCarAnimFinished();
|
|
1103
|
+
});
|
|
1104
|
+
this.personMixer.update(0);
|
|
1105
|
+
this.person.updateMatrixWorld(true);
|
|
1106
|
+
const { size } = this.getBbox(this.person);
|
|
1107
|
+
const modelScale = this.playerCapsuleHeight / size.y;
|
|
1108
|
+
this.playerCapsuleRadius = Number(Math.min(size.x, size.z).toFixed(0)) * modelScale * this.playerCapsuleRadiusRatio;
|
|
1109
|
+
this.playerCapsuleHeight = Number(size.y.toFixed(0)) * modelScale;
|
|
1110
|
+
const s = this.playerModel.scale;
|
|
1111
|
+
const r = this.playerCapsuleRadius * s;
|
|
1112
|
+
const h = this.playerCapsuleHeight * s;
|
|
1113
|
+
this.player = new THREE4.Mesh(
|
|
1114
|
+
new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75),
|
|
1115
|
+
new THREE4.MeshStandardMaterial({
|
|
1116
|
+
color: new THREE4.Color(1, 0, 0),
|
|
1117
|
+
shadowSide: THREE4.DoubleSide,
|
|
1118
|
+
depthTest: false,
|
|
1119
|
+
transparent: true,
|
|
1120
|
+
opacity: this.displayPlayer ? 0.5 : 0,
|
|
1121
|
+
wireframe: true,
|
|
1122
|
+
depthWrite: false
|
|
1123
|
+
})
|
|
1124
|
+
);
|
|
1125
|
+
this.player.geometry.translate(0, -h * 0.25, 0);
|
|
1126
|
+
this.player.capsuleInfo = {
|
|
1127
|
+
radius: r,
|
|
1128
|
+
segment: new THREE4.Line3(new THREE4.Vector3(), new THREE4.Vector3(0, -h * 0.5, 0))
|
|
1129
|
+
};
|
|
1130
|
+
this.player.name = "capsule";
|
|
1131
|
+
this.scene.add(this.player);
|
|
1132
|
+
this.reset();
|
|
1133
|
+
this.player.rotateY(this.playerModel.rotateY ?? 0);
|
|
1134
|
+
this.person.scale.multiplyScalar(modelScale * s);
|
|
1135
|
+
this.person.position.set(0, -h * 0.75, 0);
|
|
1136
|
+
this.person.traverse((child) => {
|
|
1137
|
+
if (child.name === this.playerModel?.headObjName) this.personHead = child;
|
|
885
1138
|
});
|
|
886
|
-
|
|
887
|
-
|
|
1139
|
+
this.player.add(this.person);
|
|
1140
|
+
this.reset();
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
console.error("\u52A0\u8F7D\u73A9\u5BB6\u6A21\u578B\u5931\u8D25:", e);
|
|
888
1143
|
}
|
|
889
1144
|
}
|
|
1145
|
+
// 切换玩家模型
|
|
890
1146
|
async switchPlayerModel(newPlayerModel) {
|
|
891
1147
|
const savedPos = this.player.position.clone();
|
|
892
1148
|
const savedQuat = this.player.quaternion.clone();
|
|
893
1149
|
const wasFirstPerson = this.isFirstPerson;
|
|
894
|
-
if (wasFirstPerson)
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
if (this.player) {
|
|
898
|
-
this.scene.remove(this.player);
|
|
899
|
-
}
|
|
1150
|
+
if (wasFirstPerson) this.scene.attach(this.camera);
|
|
1151
|
+
if (this.player) this.scene.remove(this.player);
|
|
900
1152
|
if (this.person) {
|
|
901
1153
|
this.player.remove(this.person);
|
|
902
1154
|
this.person = null;
|
|
@@ -915,45 +1167,76 @@ var PlayerController = class {
|
|
|
915
1167
|
this.playerSpeed *= ratio;
|
|
916
1168
|
this.playerFlySpeed *= ratio;
|
|
917
1169
|
this.curPlayerSpeed *= ratio;
|
|
918
|
-
this.
|
|
1170
|
+
this.camEpsilon *= ratio;
|
|
919
1171
|
this.minCamDistance *= ratio;
|
|
920
1172
|
this.maxCamDistance *= ratio;
|
|
921
1173
|
this.orginMaxCamDistance *= ratio;
|
|
922
1174
|
await this.loadPersonGLB();
|
|
923
1175
|
this.player.position.copy(savedPos);
|
|
924
1176
|
this.player.quaternion.copy(savedQuat);
|
|
925
|
-
if (wasFirstPerson)
|
|
926
|
-
this.setFirstPersonCamera();
|
|
927
|
-
}
|
|
1177
|
+
if (wasFirstPerson) this.setFirstPersonCamera();
|
|
928
1178
|
this.setDebug(this.displayCollider);
|
|
929
1179
|
}
|
|
1180
|
+
// 播放动画
|
|
930
1181
|
playPersonAnimationByName(name, fade = 0.18) {
|
|
931
1182
|
if (!this.personActions || this.ctPressed) return;
|
|
932
1183
|
const next = this.personActions.get(name);
|
|
933
1184
|
if (!next || this.actionState === next) return;
|
|
934
|
-
const duration = next.getClip().duration;
|
|
935
1185
|
const prev = this.actionState;
|
|
936
1186
|
next.reset();
|
|
937
1187
|
next.setEffectiveWeight(1);
|
|
938
|
-
if (name
|
|
1188
|
+
if (name === "enterCar" || name === "exitCar") {
|
|
1189
|
+
const duration = next.getClip().duration;
|
|
939
1190
|
const enterTime = this.activeVehicle?.enterVehicleTime ?? 1.5;
|
|
940
1191
|
next.setEffectiveTimeScale(duration / enterTime);
|
|
941
|
-
next.setLoop(
|
|
1192
|
+
next.setLoop(THREE4.LoopOnce, 1);
|
|
942
1193
|
next.clampWhenFinished = true;
|
|
943
1194
|
}
|
|
944
1195
|
next.play();
|
|
945
1196
|
if (prev && prev !== next) {
|
|
946
1197
|
prev.fadeOut(fade);
|
|
947
1198
|
next.fadeIn(fade);
|
|
948
|
-
} else
|
|
949
|
-
next.fadeIn(fade);
|
|
950
|
-
}
|
|
1199
|
+
} else next.fadeIn(fade);
|
|
951
1200
|
this.actionState = next;
|
|
952
1201
|
}
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1202
|
+
// 注册自定义动画
|
|
1203
|
+
registerAnimation(key, clipName, opts) {
|
|
1204
|
+
if (!this.personMixer || !this.personActions) return;
|
|
1205
|
+
const mixer = this.personMixer;
|
|
1206
|
+
const clip = this.allAnimations.find((c) => c.name === clipName);
|
|
1207
|
+
if (!clip) {
|
|
1208
|
+
console.warn(`\u627E\u4E0D\u5230 "${clipName}" \u52A8\u753B`);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const action = mixer.clipAction(clip);
|
|
1212
|
+
const timeScale = opts?.duration ? clip.duration / opts.duration : opts?.timeScale ?? 1;
|
|
1213
|
+
action.setLoop(opts?.loop === false ? THREE4.LoopOnce : THREE4.LoopRepeat, Infinity);
|
|
1214
|
+
action.clampWhenFinished = opts?.clampWhenFinished ?? false;
|
|
1215
|
+
action.setEffectiveTimeScale(timeScale);
|
|
1216
|
+
action.enabled = true;
|
|
1217
|
+
action.setEffectiveWeight(0);
|
|
1218
|
+
this.personActions.set(key, action);
|
|
1219
|
+
if (opts?.onFinished) {
|
|
1220
|
+
this.personMixer.addEventListener("finished", (ev) => {
|
|
1221
|
+
if (ev.action === action) opts.onFinished();
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
// 外部播放动画
|
|
1226
|
+
playAnimation(key, opts) {
|
|
1227
|
+
if (!this.personActions) return;
|
|
1228
|
+
if (!this.personActions.has(key)) {
|
|
1229
|
+
console.warn(`playAnimation: "${key}" \u672A\u6CE8\u518C`);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (opts?.force) {
|
|
1233
|
+
const action = this.personActions.get(key);
|
|
1234
|
+
action.reset();
|
|
1235
|
+
}
|
|
1236
|
+
this.playPersonAnimationByName(key, opts?.fade ?? 0.18);
|
|
1237
|
+
}
|
|
1238
|
+
// ==================== 车辆 ====================
|
|
1239
|
+
// 加载车辆模型
|
|
957
1240
|
async loadVehicleModel(opts) {
|
|
958
1241
|
try {
|
|
959
1242
|
if (!this.playerModel.enterCarAnim) {
|
|
@@ -961,287 +1244,31 @@ var PlayerController = class {
|
|
|
961
1244
|
}
|
|
962
1245
|
await this.initRapier();
|
|
963
1246
|
if (!this.world) return;
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const { size: originalSize } = this.getBbox(vehicleModel.scene);
|
|
975
|
-
const ratio = this.vehicleLength / Math.max(originalSize.x, originalSize.y, originalSize.z);
|
|
976
|
-
const modelScale = ratio;
|
|
977
|
-
const vehicleMixer = new THREE3.AnimationMixer(vehicleModel.scene);
|
|
978
|
-
const animations = vehicleModel.animations ?? [];
|
|
979
|
-
const vehicleActions = /* @__PURE__ */ new Map();
|
|
980
|
-
const findClip = (name) => animations.find((a) => a.name === name);
|
|
981
|
-
const openDoorClip = findClip(opts.animations?.openDoorAnim || "");
|
|
982
|
-
if (openDoorClip) {
|
|
983
|
-
const action = vehicleMixer.clipAction(openDoorClip);
|
|
984
|
-
action.setLoop(THREE3.LoopOnce, 1);
|
|
985
|
-
action.clampWhenFinished = true;
|
|
986
|
-
action.setEffectiveTimeScale(openDoorClip.duration);
|
|
987
|
-
action.enabled = true;
|
|
988
|
-
action.setEffectiveWeight(0);
|
|
989
|
-
vehicleActions.set("openDoor", action);
|
|
990
|
-
}
|
|
991
|
-
const wheelObjects = [];
|
|
992
|
-
for (const wheelName of opts.wheelsNames) {
|
|
993
|
-
let found = false;
|
|
994
|
-
vehicleModel.scene.traverse((child) => {
|
|
995
|
-
if (child.name === wheelName && !found) {
|
|
996
|
-
wheelObjects.push(child);
|
|
997
|
-
found = true;
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
if (!found) console.warn(`\u672A\u627E\u5230\u8F6E\u5B50: ${wheelName}`);
|
|
1001
|
-
}
|
|
1002
|
-
const tempGroup = new THREE3.Group();
|
|
1003
|
-
this.scene.add(tempGroup);
|
|
1004
|
-
vehicleModel.scene.scale.multiplyScalar(modelScale * scale);
|
|
1005
|
-
vehicleModel.scene.rotateY(this.vehicleParams.model.rotation);
|
|
1006
|
-
const { size, bbox, center } = this.getBbox(vehicleModel.scene);
|
|
1007
|
-
vehicleModel.scene.position.set(-center.x, -center.y, -center.z);
|
|
1008
|
-
tempGroup.add(vehicleModel.scene);
|
|
1009
|
-
tempGroup.updateMatrixWorld(true);
|
|
1010
|
-
const wheelsInfo = [];
|
|
1011
|
-
let wheelRadius = 0;
|
|
1012
|
-
let wheelWidth = 0;
|
|
1013
|
-
let suspensionRestLength = 0;
|
|
1014
|
-
let chassisHeight = 0;
|
|
1015
|
-
let wheelSizeInit = false;
|
|
1016
|
-
for (let i = 0; i < wheelObjects.length; i++) {
|
|
1017
|
-
const wheel = wheelObjects[i];
|
|
1018
|
-
const worldPos = new THREE3.Vector3();
|
|
1019
|
-
const worldQuat = new THREE3.Quaternion();
|
|
1020
|
-
const worldScale = new THREE3.Vector3();
|
|
1021
|
-
wheel.getWorldPosition(worldPos);
|
|
1022
|
-
wheel.getWorldQuaternion(worldQuat);
|
|
1023
|
-
wheel.getWorldScale(worldScale);
|
|
1024
|
-
if (!wheelSizeInit) {
|
|
1025
|
-
const { size: ws } = this.getBbox(wheel);
|
|
1026
|
-
wheelRadius = Number((Math.max(ws.x, ws.y, ws.z) / 2).toFixed(2));
|
|
1027
|
-
wheelWidth = Number(Math.min(ws.x, ws.y, ws.z).toFixed(2));
|
|
1028
|
-
suspensionRestLength = Number((wheelRadius * 2 * suspensionRestLengthRatio).toFixed(2));
|
|
1029
|
-
chassisHeight = Number((wheelRadius * 2 * chassisRatio).toFixed(2));
|
|
1030
|
-
wheelSizeInit = true;
|
|
1031
|
-
}
|
|
1032
|
-
wheelsInfo.push({
|
|
1033
|
-
axleCs: new THREE3.Vector3(0, 0, -1),
|
|
1034
|
-
position: worldPos,
|
|
1035
|
-
quaternion: worldQuat,
|
|
1036
|
-
scale: worldScale,
|
|
1037
|
-
radius: wheelRadius,
|
|
1038
|
-
width: wheelWidth,
|
|
1039
|
-
suspensionRestLength,
|
|
1040
|
-
object: wheel
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
tempGroup.remove(vehicleModel.scene);
|
|
1044
|
-
this.scene.remove(tempGroup);
|
|
1045
|
-
const vehicleGroup = new THREE3.Group();
|
|
1046
|
-
this.scene.add(vehicleGroup);
|
|
1047
|
-
vehicleGroup.add(vehicleModel.scene);
|
|
1048
|
-
vehicleGroup.updateMatrixWorld(true);
|
|
1049
|
-
const wheelWrappers = [];
|
|
1050
|
-
for (let i = 0; i < wheelsInfo.length; i++) {
|
|
1051
|
-
const wheel = wheelsInfo[i];
|
|
1052
|
-
const localPos = vehicleGroup.worldToLocal(wheel.position.clone());
|
|
1053
|
-
const wheelWrapper = new THREE3.Group();
|
|
1054
|
-
wheelWrapper.position.copy(localPos);
|
|
1055
|
-
const wheelObj = wheelsInfo[i].object;
|
|
1056
|
-
if (wheelObj.parent) wheelObj.parent.remove(wheelObj);
|
|
1057
|
-
wheelObj.position.set(0, 0, 0);
|
|
1058
|
-
wheelObj.quaternion.copy(wheel.quaternion);
|
|
1059
|
-
wheelObj.scale.copy(wheel.scale);
|
|
1060
|
-
wheelObj.updateMatrixWorld();
|
|
1061
|
-
wheelWrapper.add(wheelObj);
|
|
1062
|
-
vehicleGroup.add(wheelWrapper);
|
|
1063
|
-
wheelWrappers.push(wheelWrapper);
|
|
1064
|
-
}
|
|
1065
|
-
const halfExtents = size.clone().multiplyScalar(0.5);
|
|
1066
|
-
halfExtents.y -= chassisHeight / 2;
|
|
1067
|
-
vehicleModel.scene.position.y -= chassisHeight / 2;
|
|
1068
|
-
halfExtents.x *= 0.95;
|
|
1069
|
-
halfExtents.z *= 0.95;
|
|
1070
|
-
const chassisDesc = this.RAPIER.RigidBodyDesc.dynamic().setTranslation(
|
|
1071
|
-
opts.position.x,
|
|
1072
|
-
opts.position.y,
|
|
1073
|
-
opts.position.z
|
|
1074
|
-
).setLinearDamping(this.vehicleParams.chassis.linearDamping).setAngularDamping(this.vehicleParams.chassis.angularDamping).setCanSleep(true).setAdditionalMass(10);
|
|
1075
|
-
const chassisBody = this.world.createRigidBody(chassisDesc);
|
|
1076
|
-
const chassisCollider = this.RAPIER.ColliderDesc.cuboid(
|
|
1077
|
-
halfExtents.x,
|
|
1078
|
-
halfExtents.y,
|
|
1079
|
-
halfExtents.z
|
|
1080
|
-
);
|
|
1081
|
-
this.world.createCollider(chassisCollider, chassisBody);
|
|
1082
|
-
if (this.vehicleParams.debug.showPhysicsBox) {
|
|
1083
|
-
const debugBox = new THREE3.Mesh(
|
|
1084
|
-
new THREE3.BoxGeometry(
|
|
1085
|
-
halfExtents.x * 2,
|
|
1086
|
-
halfExtents.y * 2,
|
|
1087
|
-
halfExtents.z * 2
|
|
1088
|
-
),
|
|
1089
|
-
new THREE3.MeshBasicMaterial({
|
|
1090
|
-
color: 16711680,
|
|
1091
|
-
wireframe: true,
|
|
1092
|
-
transparent: true,
|
|
1093
|
-
opacity: 0.3
|
|
1094
|
-
})
|
|
1095
|
-
);
|
|
1096
|
-
vehicleGroup.add(debugBox);
|
|
1097
|
-
}
|
|
1098
|
-
vehicleGroup.position.copy(opts.position);
|
|
1099
|
-
vehicleGroup.updateMatrixWorld(true);
|
|
1100
|
-
const { vehicle, updateWheelVisuals } = createVehicleController(
|
|
1101
|
-
this.world,
|
|
1102
|
-
chassisBody,
|
|
1103
|
-
wheelWrappers,
|
|
1104
|
-
wheelsInfo
|
|
1105
|
-
);
|
|
1106
|
-
const vehicleInstance = {
|
|
1107
|
-
vehicleGroup,
|
|
1108
|
-
chassisBody,
|
|
1109
|
-
vehicleController: vehicle,
|
|
1110
|
-
updateWheelVisuals,
|
|
1111
|
-
vehicleMixer,
|
|
1112
|
-
vehicleActions,
|
|
1113
|
-
vehiclIsOpenDoor: false,
|
|
1114
|
-
vehicleBBox: bbox.clone(),
|
|
1115
|
-
pathPlanner: new PathPlanner(
|
|
1116
|
-
this._createObstacleCheckerFor(vehicleGroup, bbox, scale),
|
|
1117
|
-
{
|
|
1118
|
-
debugEnabled: false,
|
|
1119
|
-
scene: this.scene,
|
|
1120
|
-
scale: this.playerModel.scale
|
|
1121
|
-
}
|
|
1122
|
-
),
|
|
1123
|
-
scale,
|
|
1124
|
-
boardingPoint: opts.boardingPoint,
|
|
1125
|
-
seatOffset: opts.seatOffset ?? new THREE3.Vector3(0, 0, 0),
|
|
1126
|
-
enterVehicleTime: 1.5,
|
|
1127
|
-
chassisRatio,
|
|
1128
|
-
suspensionRestLengthRatio,
|
|
1129
|
-
size: {
|
|
1130
|
-
l: Math.max(size.x, size.z),
|
|
1131
|
-
w: Math.min(size.x, size.z),
|
|
1132
|
-
h: size.y
|
|
1133
|
-
},
|
|
1134
|
-
speedMultiplier
|
|
1135
|
-
};
|
|
1136
|
-
this.vehicles.push(vehicleInstance);
|
|
1247
|
+
const instance = await loadVehicleModel(opts, {
|
|
1248
|
+
loader: this.loader,
|
|
1249
|
+
scene: this.scene,
|
|
1250
|
+
world: this.world,
|
|
1251
|
+
RAPIER: this.RAPIER,
|
|
1252
|
+
vehicleParams: this.vehicleParams,
|
|
1253
|
+
vehicleLength: this.vehicleLength,
|
|
1254
|
+
playerScale: this.playerModel.scale
|
|
1255
|
+
});
|
|
1256
|
+
this.vehicles.push(instance);
|
|
1137
1257
|
this.setControllerTransition();
|
|
1138
|
-
} catch (
|
|
1139
|
-
console.error("\u52A0\u8F7D\u8F66\u8F86\u6A21\u578B\u5931\u8D25:",
|
|
1258
|
+
} catch (e) {
|
|
1259
|
+
console.error("\u52A0\u8F7D\u8F66\u8F86\u6A21\u578B\u5931\u8D25:", e);
|
|
1140
1260
|
}
|
|
1141
1261
|
}
|
|
1262
|
+
// 获取包围盒
|
|
1142
1263
|
getBbox(object) {
|
|
1143
|
-
const bbox = new
|
|
1144
|
-
const center = new
|
|
1145
|
-
const size = new
|
|
1264
|
+
const bbox = new THREE4.Box3().setFromObject(object);
|
|
1265
|
+
const center = new THREE4.Vector3();
|
|
1266
|
+
const size = new THREE4.Vector3();
|
|
1146
1267
|
bbox.getCenter(center);
|
|
1147
1268
|
bbox.getSize(size);
|
|
1148
1269
|
return { bbox, center, size };
|
|
1149
1270
|
}
|
|
1150
|
-
|
|
1151
|
-
* 为指定车辆创建障碍物检测器
|
|
1152
|
-
*/
|
|
1153
|
-
_createObstacleCheckerFor(vehicleGroup, bbox, scale) {
|
|
1154
|
-
return {
|
|
1155
|
-
isBlocked: (start, end) => {
|
|
1156
|
-
const vehiclePos = vehicleGroup.position;
|
|
1157
|
-
const vehicleQuat = vehicleGroup.quaternion;
|
|
1158
|
-
const center = new THREE3.Vector3();
|
|
1159
|
-
const size = new THREE3.Vector3();
|
|
1160
|
-
bbox.getCenter(center);
|
|
1161
|
-
bbox.getSize(size);
|
|
1162
|
-
center.applyQuaternion(vehicleQuat).add(vehiclePos);
|
|
1163
|
-
const halfSize = size.clone().multiplyScalar(0.5 * scale);
|
|
1164
|
-
const corners = [];
|
|
1165
|
-
for (let x = -1; x <= 1; x += 2) {
|
|
1166
|
-
for (let y = -1; y <= 1; y += 2) {
|
|
1167
|
-
for (let z = -1; z <= 1; z += 2) {
|
|
1168
|
-
const localCorner = new THREE3.Vector3(
|
|
1169
|
-
halfSize.x * x,
|
|
1170
|
-
halfSize.y * y,
|
|
1171
|
-
halfSize.z * z
|
|
1172
|
-
);
|
|
1173
|
-
const worldCorner = localCorner.applyQuaternion(vehicleQuat).add(center);
|
|
1174
|
-
corners.push(worldCorner);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
const expandedBBox = new THREE3.Box3();
|
|
1179
|
-
corners.forEach((corner) => expandedBBox.expandByPoint(corner));
|
|
1180
|
-
expandedBBox.expandByScalar(100 * this.playerModel.scale);
|
|
1181
|
-
const direction = new THREE3.Vector3().subVectors(end, start);
|
|
1182
|
-
const length = direction.length();
|
|
1183
|
-
direction.normalize();
|
|
1184
|
-
const ray = new THREE3.Ray(start, direction);
|
|
1185
|
-
const intersection = new THREE3.Vector3();
|
|
1186
|
-
const intersects = ray.intersectBox(expandedBBox, intersection);
|
|
1187
|
-
return intersects !== null && start.distanceTo(intersection) < length;
|
|
1188
|
-
},
|
|
1189
|
-
getNavigationNodes: (start, _goal) => {
|
|
1190
|
-
const nodes = [];
|
|
1191
|
-
const vehiclePos = vehicleGroup.position;
|
|
1192
|
-
const vehicleQuat = vehicleGroup.quaternion;
|
|
1193
|
-
const vehicleForward = new THREE3.Vector3(
|
|
1194
|
-
0,
|
|
1195
|
-
0,
|
|
1196
|
-
1
|
|
1197
|
-
).applyQuaternion(vehicleQuat);
|
|
1198
|
-
const vehicleRight = new THREE3.Vector3(1, 0, 0).applyQuaternion(
|
|
1199
|
-
vehicleQuat
|
|
1200
|
-
);
|
|
1201
|
-
const bboxSize = new THREE3.Vector3();
|
|
1202
|
-
bbox.getSize(bboxSize);
|
|
1203
|
-
const halfLength = bboxSize.z / 2 * scale;
|
|
1204
|
-
const halfWidth = bboxSize.x / 2 * scale;
|
|
1205
|
-
const bypassMargin = 300 * this.playerModel.scale;
|
|
1206
|
-
const extendedMargin = 500 * this.playerModel.scale;
|
|
1207
|
-
const groundY = start.y;
|
|
1208
|
-
for (const margin of [bypassMargin, extendedMargin]) {
|
|
1209
|
-
nodes.push(
|
|
1210
|
-
vehiclePos.clone().add(
|
|
1211
|
-
vehicleForward.clone().multiplyScalar(halfLength + margin)
|
|
1212
|
-
).add(
|
|
1213
|
-
vehicleRight.clone().multiplyScalar(-halfWidth - margin)
|
|
1214
|
-
).setY(groundY)
|
|
1215
|
-
);
|
|
1216
|
-
nodes.push(
|
|
1217
|
-
vehiclePos.clone().add(
|
|
1218
|
-
vehicleForward.clone().multiplyScalar(halfLength + margin)
|
|
1219
|
-
).add(
|
|
1220
|
-
vehicleRight.clone().multiplyScalar(halfWidth + margin)
|
|
1221
|
-
).setY(groundY)
|
|
1222
|
-
);
|
|
1223
|
-
nodes.push(
|
|
1224
|
-
vehiclePos.clone().add(
|
|
1225
|
-
vehicleForward.clone().multiplyScalar(-halfLength - margin)
|
|
1226
|
-
).add(
|
|
1227
|
-
vehicleRight.clone().multiplyScalar(-halfWidth - margin)
|
|
1228
|
-
).setY(groundY)
|
|
1229
|
-
);
|
|
1230
|
-
nodes.push(
|
|
1231
|
-
vehiclePos.clone().add(
|
|
1232
|
-
vehicleForward.clone().multiplyScalar(-halfLength - margin)
|
|
1233
|
-
).add(
|
|
1234
|
-
vehicleRight.clone().multiplyScalar(halfWidth + margin)
|
|
1235
|
-
).setY(groundY)
|
|
1236
|
-
);
|
|
1237
|
-
}
|
|
1238
|
-
return nodes;
|
|
1239
|
-
}
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* 开关车门动画(操作当前 activeVehicle)
|
|
1244
|
-
*/
|
|
1271
|
+
// 开关车门动画
|
|
1245
1272
|
openVehicleDoor(isOpen = true) {
|
|
1246
1273
|
const v = this.activeVehicle;
|
|
1247
1274
|
if (!v?.vehicleActions) return;
|
|
@@ -1259,112 +1286,70 @@ var PlayerController = class {
|
|
|
1259
1286
|
next.time = duration;
|
|
1260
1287
|
v.vehiclIsOpenDoor = false;
|
|
1261
1288
|
}
|
|
1262
|
-
next.setLoop(
|
|
1289
|
+
next.setLoop(THREE4.LoopOnce, 1);
|
|
1263
1290
|
next.clampWhenFinished = true;
|
|
1264
1291
|
next.play();
|
|
1265
1292
|
}
|
|
1266
|
-
|
|
1267
|
-
* 上车:自动寻找最近的车辆
|
|
1268
|
-
*/
|
|
1293
|
+
// 触发上车流程
|
|
1269
1294
|
enterVehicle() {
|
|
1270
|
-
if (this.vehicles.length
|
|
1295
|
+
if (!this.vehicles.length || this.isMovingToBoardingPoint) return;
|
|
1271
1296
|
let nearestVehicle = null;
|
|
1272
1297
|
let nearestDist = Infinity;
|
|
1273
1298
|
let nearBoardingPointWorld = null;
|
|
1274
1299
|
for (const v2 of this.vehicles) {
|
|
1275
|
-
const
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
boardingPointWorld.copy(boardingPointLocal)
|
|
1279
|
-
);
|
|
1280
|
-
const dist = this.player.position.distanceTo(boardingPointWorld);
|
|
1300
|
+
const boardingLocal = v2.boardingPoint.clone().multiplyScalar(v2.scale);
|
|
1301
|
+
const boardingWorld = v2.vehicleGroup.localToWorld(boardingLocal);
|
|
1302
|
+
const dist = this.player.position.distanceTo(boardingWorld);
|
|
1281
1303
|
if (dist < 800 * this.playerModel.scale && dist < nearestDist) {
|
|
1282
1304
|
nearestDist = dist;
|
|
1283
1305
|
nearestVehicle = v2;
|
|
1284
|
-
nearBoardingPointWorld =
|
|
1306
|
+
nearBoardingPointWorld = boardingWorld;
|
|
1285
1307
|
}
|
|
1286
1308
|
}
|
|
1287
1309
|
if (!nearestVehicle || !nearBoardingPointWorld) return;
|
|
1288
1310
|
this.activeVehicle = nearestVehicle;
|
|
1289
1311
|
const v = nearestVehicle;
|
|
1290
1312
|
const vel = v.chassisBody.linvel();
|
|
1291
|
-
|
|
1292
|
-
if (horizSpeed > 0.1) return;
|
|
1313
|
+
if (Math.sqrt(vel.x ** 2 + vel.z ** 2) > 0.1) return;
|
|
1293
1314
|
this.boardingPointWorld = nearBoardingPointWorld;
|
|
1294
|
-
|
|
1295
|
-
const path = v.pathPlanner.findPath(
|
|
1296
|
-
this.player.position.clone(),
|
|
1297
|
-
this.boardingPointWorld
|
|
1298
|
-
);
|
|
1299
|
-
this.boardingWaypoints = path;
|
|
1315
|
+
this.boardingWaypoints = v.pathPlanner.findPath(this.player.position.clone(), nearBoardingPointWorld);
|
|
1300
1316
|
this.currentWaypointIndex = 0;
|
|
1301
|
-
this.boardingTargetDir =
|
|
1317
|
+
this.boardingTargetDir = new THREE4.Vector3(0, 0, 1).applyQuaternion(v.vehicleGroup.quaternion).normalize();
|
|
1302
1318
|
this.isMovingToBoardingPoint = true;
|
|
1303
1319
|
this.playPersonAnimationByName("walking");
|
|
1304
1320
|
}
|
|
1305
|
-
|
|
1306
|
-
* 走向上车点
|
|
1307
|
-
*/
|
|
1321
|
+
// 寻路移动到上车点
|
|
1308
1322
|
updateMoveToBoardingPoint(delta) {
|
|
1309
|
-
if (!this.isMovingToBoardingPoint || !this.boardingTargetDir || this.boardingWaypoints.length
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1323
|
+
if (!this.isMovingToBoardingPoint || !this.boardingTargetDir || !this.boardingWaypoints.length) return;
|
|
1312
1324
|
if (this.currentWaypointIndex >= this.boardingWaypoints.length) {
|
|
1313
1325
|
this.finalizeBoarding(delta);
|
|
1314
1326
|
return;
|
|
1315
1327
|
}
|
|
1316
|
-
const
|
|
1328
|
+
const waypoint = this.boardingWaypoints[this.currentWaypointIndex];
|
|
1317
1329
|
const currentPos = this.player.position.clone();
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
0,
|
|
1328
|
-
currentWaypoint.z - currentPos.z
|
|
1329
|
-
).normalize();
|
|
1330
|
-
const moveDistance = Math.min(
|
|
1331
|
-
this.boardingMoveSpeed * this.playerModel.scale * delta,
|
|
1332
|
-
horizontalDistance
|
|
1333
|
-
);
|
|
1334
|
-
this.player.position.add(moveDir.multiplyScalar(moveDistance));
|
|
1335
|
-
const lookTarget = this.player.position.clone().add(moveDir);
|
|
1336
|
-
this.targetMat.lookAt(
|
|
1337
|
-
this.player.position,
|
|
1338
|
-
lookTarget,
|
|
1339
|
-
this.player.up
|
|
1340
|
-
);
|
|
1341
|
-
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
1342
|
-
this.targetQuat.multiply(this.flip180Quat);
|
|
1343
|
-
const rotateAlpha = Math.min(1, this.boardingRotateSpeed * delta);
|
|
1344
|
-
this.player.quaternion.slerp(this.targetQuat, rotateAlpha);
|
|
1330
|
+
const isLast = this.currentWaypointIndex === this.boardingWaypoints.length - 1;
|
|
1331
|
+
const threshold = isLast ? 0 : 10 * this.playerModel.scale;
|
|
1332
|
+
const horizDist = new THREE4.Vector2(waypoint.x - currentPos.x, waypoint.z - currentPos.z).length();
|
|
1333
|
+
if (horizDist > threshold) {
|
|
1334
|
+
const moveDir = new THREE4.Vector3(waypoint.x - currentPos.x, 0, waypoint.z - currentPos.z).normalize();
|
|
1335
|
+
this.player.position.add(moveDir.clone().multiplyScalar(Math.min(this.boardingMoveSpeed * this.playerModel.scale * delta, horizDist)));
|
|
1336
|
+
this.targetMat.lookAt(this.player.position, this.player.position.clone().add(moveDir), this.player.up);
|
|
1337
|
+
this.targetQuat.setFromRotationMatrix(this.targetMat).multiply(this.flip180Quat);
|
|
1338
|
+
this.player.quaternion.slerp(this.targetQuat, Math.min(1, this.boardingRotateSpeed * delta));
|
|
1345
1339
|
} else {
|
|
1346
1340
|
this.currentWaypointIndex++;
|
|
1347
1341
|
}
|
|
1348
1342
|
}
|
|
1349
|
-
|
|
1350
|
-
* 完成上车
|
|
1351
|
-
*/
|
|
1343
|
+
// 最终对齐朝向
|
|
1352
1344
|
finalizeBoarding(delta) {
|
|
1353
1345
|
const v = this.activeVehicle;
|
|
1354
1346
|
if (!this.boardingTargetDir || !v || !this.isMovingToBoardingPoint) return;
|
|
1355
|
-
const currentDir = new
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
const lookTarget = this.player.position.clone().add(targetDir);
|
|
1360
|
-
this.targetMat.lookAt(
|
|
1361
|
-
this.player.position,
|
|
1362
|
-
lookTarget,
|
|
1363
|
-
this.player.up
|
|
1364
|
-
);
|
|
1347
|
+
const currentDir = new THREE4.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion).normalize();
|
|
1348
|
+
if (currentDir.angleTo(this.boardingTargetDir) > 0.01) {
|
|
1349
|
+
const lookTarget = this.player.position.clone().add(this.boardingTargetDir);
|
|
1350
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
1365
1351
|
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
1366
|
-
|
|
1367
|
-
this.player.quaternion.slerp(this.targetQuat, rotateAlpha);
|
|
1352
|
+
this.player.quaternion.slerp(this.targetQuat, Math.min(1, this.boardingRotateSpeed * delta));
|
|
1368
1353
|
} else {
|
|
1369
1354
|
this.boardingWaypoints = [];
|
|
1370
1355
|
this.currentWaypointIndex = 0;
|
|
@@ -1378,23 +1363,20 @@ var PlayerController = class {
|
|
|
1378
1363
|
this.player.quaternion.multiply(this.flip180Quat);
|
|
1379
1364
|
}
|
|
1380
1365
|
}
|
|
1366
|
+
// 上车动画完成
|
|
1381
1367
|
onEnterCarAnimFinished() {
|
|
1382
1368
|
const v = this.activeVehicle;
|
|
1383
1369
|
if (!v || !this.isMovingToBoardingPoint) return;
|
|
1384
1370
|
this.player.updateMatrixWorld(true);
|
|
1385
1371
|
const offsetY = this.boardingPointWorld.y - this.player.position.y;
|
|
1386
1372
|
this.controllerMode = 1;
|
|
1387
|
-
this.
|
|
1373
|
+
this.syncMobileControllerMode();
|
|
1388
1374
|
this.setOverShoulderView(false);
|
|
1389
1375
|
v.vehicleGroup.attach(this.player);
|
|
1390
|
-
this.player.position.add(
|
|
1391
|
-
v.seatOffset.clone().multiplyScalar(v.scale).add(new THREE3.Vector3(0, offsetY, 0))
|
|
1392
|
-
);
|
|
1376
|
+
this.player.position.add(v.seatOffset.clone().multiplyScalar(v.scale).add(new THREE4.Vector3(0, offsetY, 0)));
|
|
1393
1377
|
this.isMovingToBoardingPoint = false;
|
|
1394
1378
|
}
|
|
1395
|
-
|
|
1396
|
-
* 下车
|
|
1397
|
-
*/
|
|
1379
|
+
// 触发下车流程
|
|
1398
1380
|
exitVehicle() {
|
|
1399
1381
|
const v = this.activeVehicle;
|
|
1400
1382
|
if (!v) return;
|
|
@@ -1403,9 +1385,7 @@ var PlayerController = class {
|
|
|
1403
1385
|
this.currentWaypointIndex = 0;
|
|
1404
1386
|
this.boardingTargetDir = null;
|
|
1405
1387
|
const vel = v.chassisBody.linvel();
|
|
1406
|
-
|
|
1407
|
-
const isStationary = horizSpeed < 0.1;
|
|
1408
|
-
if (isStationary) {
|
|
1388
|
+
if (Math.sqrt(vel.x ** 2 + vel.z ** 2) < 0.1) {
|
|
1409
1389
|
this.playPersonAnimationByName("exitCar");
|
|
1410
1390
|
this.isExitAnimPlaying = true;
|
|
1411
1391
|
this.closeExitDoorTriggered = false;
|
|
@@ -1414,95 +1394,90 @@ var PlayerController = class {
|
|
|
1414
1394
|
}
|
|
1415
1395
|
this.openVehicleDoor(true);
|
|
1416
1396
|
this.controllerMode = 0;
|
|
1417
|
-
this.
|
|
1397
|
+
this.syncMobileControllerMode();
|
|
1418
1398
|
this.setOverShoulderView(this.enableOverShoulderView);
|
|
1419
1399
|
this.scene.attach(this.player);
|
|
1420
|
-
if (this.isFirstPerson)
|
|
1421
|
-
this.setFirstPersonCamera();
|
|
1422
|
-
}
|
|
1400
|
+
if (this.isFirstPerson) this.setFirstPersonCamera();
|
|
1423
1401
|
this.setControllerTransition();
|
|
1424
1402
|
}
|
|
1425
|
-
// ====================
|
|
1403
|
+
// ==================== 相机与视角 ====================
|
|
1404
|
+
// 切换第一/三人称
|
|
1426
1405
|
changeView() {
|
|
1427
1406
|
this.isFirstPerson = !this.isFirstPerson;
|
|
1428
1407
|
if (this.isFirstPerson) {
|
|
1429
|
-
|
|
1408
|
+
const camWorldDir = new THREE4.Vector3();
|
|
1409
|
+
this.camera.getWorldDirection(camWorldDir);
|
|
1410
|
+
const flatDir = new THREE4.Vector3(camWorldDir.x, 0, camWorldDir.z).normalize();
|
|
1411
|
+
if (flatDir.lengthSq() > 1e-3) {
|
|
1412
|
+
const yAngle = Math.atan2(flatDir.x, flatDir.z);
|
|
1413
|
+
this.player.rotation.set(0, yAngle + Math.PI, 0);
|
|
1414
|
+
}
|
|
1415
|
+
const vertAngle = Math.asin(THREE4.MathUtils.clamp(-camWorldDir.y, -1, 1));
|
|
1416
|
+
this.setFirstPersonCamera(vertAngle);
|
|
1430
1417
|
this.setOverShoulderView(false);
|
|
1431
1418
|
} else {
|
|
1432
1419
|
this.controls.enabled = true;
|
|
1433
1420
|
this.scene.attach(this.camera);
|
|
1434
|
-
const
|
|
1435
|
-
const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(
|
|
1436
|
-
this.player.quaternion
|
|
1437
|
-
);
|
|
1421
|
+
const dir = new THREE4.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
|
|
1438
1422
|
const angle = Math.atan2(dir.z, dir.x);
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
Math.sin(angle) * 400 * this.playerModel.scale
|
|
1443
|
-
);
|
|
1444
|
-
this.camera.position.copy(worldPos).add(offset);
|
|
1445
|
-
this.controls.target.copy(worldPos);
|
|
1423
|
+
const s = this.playerModel.scale;
|
|
1424
|
+
this.camera.position.copy(this.player.position).add(new THREE4.Vector3(Math.cos(angle) * 400 * s, 200 * s, Math.sin(angle) * 400 * s));
|
|
1425
|
+
this.controls.target.copy(this.player.position);
|
|
1446
1426
|
this.controls.enableZoom = this.enableZoom;
|
|
1447
1427
|
this.setOverShoulderView(this.enableOverShoulderView);
|
|
1448
1428
|
}
|
|
1449
1429
|
this.setPointerLock();
|
|
1450
1430
|
}
|
|
1451
|
-
|
|
1431
|
+
// 设置第一人称相机
|
|
1432
|
+
setFirstPersonCamera(vertAngle = 0) {
|
|
1452
1433
|
this.controls.enabled = false;
|
|
1453
1434
|
if (this.personHead) {
|
|
1454
|
-
this.personHead
|
|
1435
|
+
this.personHead.attach(this.camera);
|
|
1455
1436
|
this.camera.position.set(0, 10, 20);
|
|
1456
1437
|
} else {
|
|
1457
1438
|
this.player.attach(this.camera);
|
|
1458
|
-
this.camera.position.set(
|
|
1459
|
-
0,
|
|
1460
|
-
40 * this.playerModel.scale,
|
|
1461
|
-
30 * this.playerModel.scale
|
|
1462
|
-
);
|
|
1439
|
+
this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
|
|
1463
1440
|
}
|
|
1464
|
-
this.camera.rotation.set(
|
|
1441
|
+
this.camera.rotation.set(
|
|
1442
|
+
THREE4.MathUtils.clamp(vertAngle, -1.1, 1.4),
|
|
1443
|
+
Math.PI,
|
|
1444
|
+
0
|
|
1445
|
+
);
|
|
1465
1446
|
this.controls.enableZoom = false;
|
|
1466
1447
|
}
|
|
1448
|
+
// 设置鼠标锁定
|
|
1467
1449
|
setPointerLock() {
|
|
1450
|
+
if (!document.body.requestPointerLock) return;
|
|
1468
1451
|
if ((this.thirdMouseMode === 0 || this.thirdMouseMode === 1) && !this.isFirstPerson || this.isFirstPerson) {
|
|
1469
1452
|
document.body.requestPointerLock();
|
|
1470
1453
|
} else {
|
|
1471
1454
|
document.exitPointerLock();
|
|
1472
1455
|
}
|
|
1473
1456
|
}
|
|
1457
|
+
// 初始相机位置
|
|
1474
1458
|
setCameraPos() {
|
|
1475
1459
|
requestAnimationFrame(() => {
|
|
1476
|
-
if (this.isFirstPerson) {
|
|
1477
|
-
|
|
1478
|
-
this.camera.position.set(
|
|
1479
|
-
0,
|
|
1480
|
-
40 * this.playerModel.scale,
|
|
1481
|
-
30 * this.playerModel.scale
|
|
1482
|
-
);
|
|
1483
|
-
} else {
|
|
1484
|
-
const worldPos = this.player.position.clone();
|
|
1485
|
-
const dir = new THREE3.Vector3(0, 0, -1).applyQuaternion(
|
|
1486
|
-
this.player.quaternion
|
|
1487
|
-
);
|
|
1460
|
+
if (!this.isFirstPerson) {
|
|
1461
|
+
const dir = new THREE4.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
|
|
1488
1462
|
const angle = Math.atan2(dir.z, dir.x);
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
200 * this.playerModel.scale,
|
|
1492
|
-
Math.sin(angle) * 400 * this.playerModel.scale
|
|
1493
|
-
);
|
|
1494
|
-
this.camera.position.copy(worldPos).add(offset);
|
|
1463
|
+
const s = this.playerModel.scale;
|
|
1464
|
+
this.camera.position.copy(this.player.position).add(new THREE4.Vector3(Math.cos(angle) * 400 * s, 200 * s, Math.sin(angle) * 400 * s));
|
|
1495
1465
|
this.controls.enableZoom = this.enableZoom;
|
|
1466
|
+
} else {
|
|
1467
|
+
this.person.add(this.camera);
|
|
1468
|
+
this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
|
|
1496
1469
|
}
|
|
1497
1470
|
this.camera.updateProjectionMatrix();
|
|
1498
1471
|
});
|
|
1499
1472
|
}
|
|
1473
|
+
// 初始化轨道控制
|
|
1500
1474
|
setControls() {
|
|
1501
1475
|
this.controls.enableZoom = this.enableZoom;
|
|
1502
|
-
this.controls.rotateSpeed = this.
|
|
1476
|
+
this.controls.rotateSpeed = this.mouseSensitivity * 0.05;
|
|
1503
1477
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
1504
1478
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
1505
1479
|
}
|
|
1480
|
+
// 重置轨道控制
|
|
1506
1481
|
resetControls() {
|
|
1507
1482
|
if (!this.controls) return;
|
|
1508
1483
|
this.controls.enabled = true;
|
|
@@ -1512,214 +1487,131 @@ var PlayerController = class {
|
|
|
1512
1487
|
this.controls.enableZoom = true;
|
|
1513
1488
|
this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
|
|
1514
1489
|
}
|
|
1490
|
+
// 视角旋转处理
|
|
1515
1491
|
setToward(dx, dy, speed) {
|
|
1516
|
-
|
|
1492
|
+
const sens = this.mouseSensitivity;
|
|
1493
|
+
if (this.controllerMode === 0) {
|
|
1517
1494
|
if (this.isFirstPerson) {
|
|
1518
1495
|
if (this.isMovingToBoardingPoint) return;
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
this.player.rotateY(yaw);
|
|
1522
|
-
this.camera.rotation.x = THREE3.MathUtils.clamp(
|
|
1523
|
-
this.camera.rotation.x + pitch,
|
|
1524
|
-
-1.1,
|
|
1525
|
-
1.4
|
|
1526
|
-
);
|
|
1496
|
+
this.player.rotateY(-dx * speed * sens);
|
|
1497
|
+
this.camera.rotation.x = THREE4.MathUtils.clamp(this.camera.rotation.x + -dy * speed * sens, -1.1, 1.4);
|
|
1527
1498
|
} else {
|
|
1528
|
-
|
|
1529
|
-
const deltaX = -dx * speed * sensitivity;
|
|
1530
|
-
const deltaY = -dy * speed * sensitivity;
|
|
1531
|
-
const target = this.player.position.clone();
|
|
1532
|
-
const distance = this.camera.position.distanceTo(target);
|
|
1533
|
-
const currentPosition = this.camera.position.clone().sub(target);
|
|
1534
|
-
let theta = Math.atan2(currentPosition.x, currentPosition.z);
|
|
1535
|
-
let phi = Math.acos(currentPosition.y / distance);
|
|
1536
|
-
theta += deltaX;
|
|
1537
|
-
phi += deltaY;
|
|
1538
|
-
phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
|
|
1539
|
-
const newX = distance * Math.sin(phi) * Math.sin(theta);
|
|
1540
|
-
const newY = distance * Math.cos(phi);
|
|
1541
|
-
const newZ = distance * Math.sin(phi) * Math.cos(theta);
|
|
1542
|
-
this.camera.position.set(
|
|
1543
|
-
target.x + newX,
|
|
1544
|
-
target.y + newY,
|
|
1545
|
-
target.z + newZ
|
|
1546
|
-
);
|
|
1547
|
-
this.camera.lookAt(target);
|
|
1499
|
+
this.orbitCamera(this.player.position, -dx * speed * sens, -dy * speed * sens);
|
|
1548
1500
|
}
|
|
1549
1501
|
} else {
|
|
1550
1502
|
const v = this.activeVehicle;
|
|
1551
1503
|
if (!v) return;
|
|
1552
1504
|
if (this.isFirstPerson) {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
this.camera.rotation.y = THREE3.MathUtils.clamp(
|
|
1556
|
-
this.camera.rotation.y + yaw,
|
|
1557
|
-
Math.PI * (3 / 4),
|
|
1558
|
-
Math.PI * (5 / 4)
|
|
1559
|
-
);
|
|
1560
|
-
this.camera.rotation.x = THREE3.MathUtils.clamp(
|
|
1561
|
-
this.camera.rotation.x + pitch,
|
|
1562
|
-
0,
|
|
1563
|
-
Math.PI * (1 / 3)
|
|
1564
|
-
);
|
|
1505
|
+
this.camera.rotation.y = THREE4.MathUtils.clamp(this.camera.rotation.y + -dx * speed * sens, Math.PI * (3 / 4), Math.PI * (5 / 4));
|
|
1506
|
+
this.camera.rotation.x = THREE4.MathUtils.clamp(this.camera.rotation.x + -dy * speed * sens, 0, Math.PI * (1 / 3));
|
|
1565
1507
|
} else {
|
|
1566
|
-
|
|
1567
|
-
const deltaX = -dx * speed * sensitivity;
|
|
1568
|
-
const deltaY = -dy * speed * sensitivity;
|
|
1569
|
-
const target = v.vehicleGroup.position.clone();
|
|
1570
|
-
const distance = this.camera.position.distanceTo(target);
|
|
1571
|
-
const currentPosition = this.camera.position.clone().sub(target);
|
|
1572
|
-
let theta = Math.atan2(currentPosition.x, currentPosition.z);
|
|
1573
|
-
let phi = Math.acos(currentPosition.y / distance);
|
|
1574
|
-
theta += deltaX;
|
|
1575
|
-
phi += deltaY;
|
|
1576
|
-
phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
|
|
1577
|
-
const newX = distance * Math.sin(phi) * Math.sin(theta);
|
|
1578
|
-
const newY = distance * Math.cos(phi);
|
|
1579
|
-
const newZ = distance * Math.sin(phi) * Math.cos(theta);
|
|
1580
|
-
this.camera.position.set(
|
|
1581
|
-
target.x + newX,
|
|
1582
|
-
target.y + newY,
|
|
1583
|
-
target.z + newZ
|
|
1584
|
-
);
|
|
1585
|
-
this.camera.lookAt(target);
|
|
1508
|
+
this.orbitCamera(v.vehicleGroup.position, -dx * speed * sens, -dy * speed * sens);
|
|
1586
1509
|
}
|
|
1587
1510
|
}
|
|
1588
1511
|
}
|
|
1512
|
+
// 球面轨道旋转
|
|
1513
|
+
orbitCamera(target, deltaX, deltaY) {
|
|
1514
|
+
const distance = this.camera.position.distanceTo(target);
|
|
1515
|
+
const cur = this.camera.position.clone().sub(target);
|
|
1516
|
+
let theta = Math.atan2(cur.x, cur.z) + deltaX;
|
|
1517
|
+
let phi = Math.acos(THREE4.MathUtils.clamp(cur.y / distance, -1, 1)) + deltaY;
|
|
1518
|
+
phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
|
|
1519
|
+
this.camera.position.set(
|
|
1520
|
+
target.x + distance * Math.sin(phi) * Math.sin(theta),
|
|
1521
|
+
target.y + distance * Math.cos(phi),
|
|
1522
|
+
target.z + distance * Math.sin(phi) * Math.cos(theta)
|
|
1523
|
+
);
|
|
1524
|
+
this.camera.lookAt(target);
|
|
1525
|
+
}
|
|
1526
|
+
// ==================== 碰撞体构建 ====================
|
|
1527
|
+
// 补全几何属性
|
|
1528
|
+
ensureAttributesMinimal(geom) {
|
|
1529
|
+
if (!geom.attributes.position) return null;
|
|
1530
|
+
if (!geom.attributes.normal) geom.computeVertexNormals();
|
|
1531
|
+
if (!geom.attributes.uv) {
|
|
1532
|
+
const count = geom.attributes.position.count;
|
|
1533
|
+
geom.setAttribute("uv", new THREE4.BufferAttribute(new Float32Array(count * 2), 2));
|
|
1534
|
+
}
|
|
1535
|
+
return geom;
|
|
1536
|
+
}
|
|
1537
|
+
// 统一几何属性格式
|
|
1589
1538
|
unifiedAttribute(collected) {
|
|
1590
1539
|
const attrMap = /* @__PURE__ */ new Map();
|
|
1591
1540
|
const attrConflict = /* @__PURE__ */ new Set();
|
|
1592
|
-
const
|
|
1593
|
-
for (const g of collected)
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
if (!requiredAttrs.has(name)) {
|
|
1597
|
-
g.deleteAttribute(name);
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1541
|
+
const required = /* @__PURE__ */ new Set(["position", "normal", "uv"]);
|
|
1542
|
+
for (const g of collected)
|
|
1543
|
+
for (const name of Object.keys(g.attributes))
|
|
1544
|
+
if (!required.has(name)) g.deleteAttribute(name);
|
|
1601
1545
|
for (const g of collected) {
|
|
1602
1546
|
for (const name of Object.keys(g.attributes)) {
|
|
1603
1547
|
const attr = g.attributes[name];
|
|
1604
1548
|
const ctor = attr.array.constructor;
|
|
1605
|
-
const itemSize = attr.itemSize;
|
|
1606
|
-
const normalized = attr.normalized;
|
|
1607
1549
|
if (!attrMap.has(name)) {
|
|
1608
|
-
attrMap.set(name, {
|
|
1609
|
-
itemSize,
|
|
1610
|
-
arrayCtor: ctor,
|
|
1611
|
-
examples: 1,
|
|
1612
|
-
normalized
|
|
1613
|
-
});
|
|
1550
|
+
attrMap.set(name, { itemSize: attr.itemSize, arrayCtor: ctor, examples: 1, normalized: attr.normalized });
|
|
1614
1551
|
} else {
|
|
1615
1552
|
const m = attrMap.get(name);
|
|
1616
|
-
if (m.itemSize !== itemSize || m.arrayCtor !== ctor || m.normalized !== normalized)
|
|
1617
|
-
|
|
1618
|
-
} else {
|
|
1619
|
-
m.examples++;
|
|
1620
|
-
}
|
|
1553
|
+
if (m.itemSize !== attr.itemSize || m.arrayCtor !== ctor || m.normalized !== attr.normalized) attrConflict.add(name);
|
|
1554
|
+
else m.examples++;
|
|
1621
1555
|
}
|
|
1622
1556
|
}
|
|
1623
1557
|
}
|
|
1624
|
-
|
|
1625
|
-
for (const g of collected)
|
|
1626
|
-
|
|
1627
|
-
if (g.attributes[name]) g.deleteAttribute(name);
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
for (const name of attrConflict) attrMap.delete(name);
|
|
1558
|
+
for (const name of attrConflict) {
|
|
1559
|
+
for (const g of collected) if (g.attributes[name]) g.deleteAttribute(name);
|
|
1560
|
+
attrMap.delete(name);
|
|
1631
1561
|
}
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
const count = g.attributes.position.count;
|
|
1635
|
-
for (const name of attrNames) {
|
|
1562
|
+
for (const [name, meta] of attrMap) {
|
|
1563
|
+
for (const g of collected) {
|
|
1636
1564
|
if (!g.attributes[name]) {
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
const array = new meta.arrayCtor(len);
|
|
1640
|
-
g.setAttribute(
|
|
1641
|
-
name,
|
|
1642
|
-
new THREE3.BufferAttribute(
|
|
1643
|
-
array,
|
|
1644
|
-
meta.itemSize,
|
|
1645
|
-
meta.normalized
|
|
1646
|
-
)
|
|
1647
|
-
);
|
|
1565
|
+
const count = g.attributes.position.count;
|
|
1566
|
+
g.setAttribute(name, new THREE4.BufferAttribute(new meta.arrayCtor(count * meta.itemSize), meta.itemSize, meta.normalized));
|
|
1648
1567
|
}
|
|
1649
1568
|
}
|
|
1650
1569
|
}
|
|
1651
1570
|
return collected;
|
|
1652
1571
|
}
|
|
1572
|
+
// 构建静态 BVH
|
|
1653
1573
|
async createBVH(meshUrl = "") {
|
|
1654
1574
|
await this.initLoader();
|
|
1575
|
+
const collectMesh = (mesh) => {
|
|
1576
|
+
try {
|
|
1577
|
+
let geom = mesh.geometry.clone();
|
|
1578
|
+
geom.applyMatrix4(mesh.matrixWorld);
|
|
1579
|
+
if (geom.index) geom = geom.toNonIndexed();
|
|
1580
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1581
|
+
if (safe) this.collected.push(safe);
|
|
1582
|
+
} catch (e) {
|
|
1583
|
+
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1655
1586
|
if (meshUrl === "") {
|
|
1656
1587
|
if (this.collider) {
|
|
1657
1588
|
this.scene.remove(this.collider);
|
|
1658
1589
|
this.collider = null;
|
|
1659
1590
|
}
|
|
1660
1591
|
this.scene.traverse((c) => {
|
|
1661
|
-
const
|
|
1662
|
-
if (
|
|
1663
|
-
try {
|
|
1664
|
-
let geom = mesh.geometry.clone();
|
|
1665
|
-
geom.applyMatrix4(mesh.matrixWorld);
|
|
1666
|
-
if (geom.index) geom = geom.toNonIndexed();
|
|
1667
|
-
const safe = this.ensureAttributesMinimal(geom);
|
|
1668
|
-
if (safe) this.collected.push(safe);
|
|
1669
|
-
} catch (e) {
|
|
1670
|
-
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1592
|
+
const m = c;
|
|
1593
|
+
if (m?.isMesh && m.geometry && c.name !== "capsule") collectMesh(m);
|
|
1673
1594
|
});
|
|
1674
|
-
if (!this.collected.length) return;
|
|
1675
|
-
this.collected = this.unifiedAttribute(this.collected);
|
|
1676
1595
|
} else {
|
|
1677
1596
|
const gltf = await this.loader.loadAsync(meshUrl);
|
|
1678
|
-
const
|
|
1679
|
-
if (
|
|
1680
|
-
|
|
1681
|
-
let geom = mesh.geometry.clone();
|
|
1682
|
-
geom.applyMatrix4(mesh.matrixWorld);
|
|
1683
|
-
if (geom.index) geom = geom.toNonIndexed();
|
|
1684
|
-
const safe = this.ensureAttributesMinimal(geom);
|
|
1685
|
-
if (safe) this.collected.push(safe);
|
|
1597
|
+
const root = gltf.scene.children[0];
|
|
1598
|
+
if (root?.geometry) {
|
|
1599
|
+
collectMesh(root);
|
|
1686
1600
|
} else {
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
if (mesh?.isMesh && mesh.geometry && c.name !== "capsule") {
|
|
1690
|
-
try {
|
|
1691
|
-
let geom = mesh.geometry.clone();
|
|
1692
|
-
geom.applyMatrix4(mesh.matrixWorld);
|
|
1693
|
-
if (geom.index) geom = geom.toNonIndexed();
|
|
1694
|
-
const safe = this.ensureAttributesMinimal(geom);
|
|
1695
|
-
if (safe) this.collected.push(safe);
|
|
1696
|
-
} catch (e) {
|
|
1697
|
-
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1601
|
+
root?.traverse((c) => {
|
|
1602
|
+
if (c?.isMesh && c.geometry && c.name !== "capsule") collectMesh(c);
|
|
1700
1603
|
});
|
|
1701
|
-
if (!this.collected.length) return;
|
|
1702
|
-
this.collected = this.unifiedAttribute(this.collected);
|
|
1703
1604
|
}
|
|
1704
1605
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
);
|
|
1606
|
+
if (!this.collected.length) return;
|
|
1607
|
+
this.collected = this.unifiedAttribute(this.collected);
|
|
1608
|
+
const merged = BufferGeometryUtils.mergeGeometries(this.collected, false);
|
|
1709
1609
|
if (!merged) {
|
|
1710
1610
|
console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
|
|
1711
1611
|
return;
|
|
1712
1612
|
}
|
|
1713
1613
|
merged.boundsTree = new MeshBVH(merged, { maxDepth: 100 });
|
|
1714
|
-
this.collider = new
|
|
1715
|
-
merged,
|
|
1716
|
-
new THREE3.MeshBasicMaterial({
|
|
1717
|
-
opacity: 0.5,
|
|
1718
|
-
transparent: true,
|
|
1719
|
-
wireframe: true,
|
|
1720
|
-
depthTest: true
|
|
1721
|
-
})
|
|
1722
|
-
);
|
|
1614
|
+
this.collider = new THREE4.Mesh(merged, new THREE4.MeshBasicMaterial({ opacity: 0.5, transparent: true, wireframe: true, depthTest: true }));
|
|
1723
1615
|
if (this.displayCollider) this.scene.add(this.collider);
|
|
1724
1616
|
if (this.displayVisualizer) {
|
|
1725
1617
|
if (this.visualizer) this.scene.remove(this.visualizer);
|
|
@@ -1728,165 +1620,108 @@ var PlayerController = class {
|
|
|
1728
1620
|
}
|
|
1729
1621
|
this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
|
|
1730
1622
|
}
|
|
1623
|
+
// 构建动态 BVH
|
|
1731
1624
|
createDynamicBVH(objects = []) {
|
|
1732
1625
|
if (this.dynamicCollider) {
|
|
1733
1626
|
this.scene.remove(this.dynamicCollider);
|
|
1734
1627
|
this.dynamicCollider = null;
|
|
1735
1628
|
}
|
|
1736
1629
|
this.dynamicCollected = [];
|
|
1737
|
-
objects.forEach((
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
|
|
1749
|
-
}
|
|
1630
|
+
objects.forEach((obj) => obj.traverse((c) => {
|
|
1631
|
+
const m = c;
|
|
1632
|
+
if (m?.isMesh && m.geometry && c.name !== "capsule") {
|
|
1633
|
+
try {
|
|
1634
|
+
let geom = m.geometry.clone();
|
|
1635
|
+
geom.applyMatrix4(m.matrixWorld);
|
|
1636
|
+
if (geom.index) geom = geom.toNonIndexed();
|
|
1637
|
+
const safe = this.ensureAttributesMinimal(geom);
|
|
1638
|
+
if (safe) this.dynamicCollected.push(safe);
|
|
1639
|
+
} catch (e) {
|
|
1640
|
+
console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", m, e);
|
|
1750
1641
|
}
|
|
1751
|
-
}
|
|
1752
|
-
});
|
|
1642
|
+
}
|
|
1643
|
+
}));
|
|
1753
1644
|
if (!this.dynamicCollected.length) return;
|
|
1754
1645
|
this.dynamicCollected = this.unifiedAttribute(this.dynamicCollected);
|
|
1755
|
-
const merged = BufferGeometryUtils.mergeGeometries(
|
|
1756
|
-
this.dynamicCollected,
|
|
1757
|
-
false
|
|
1758
|
-
);
|
|
1646
|
+
const merged = BufferGeometryUtils.mergeGeometries(this.dynamicCollected, false);
|
|
1759
1647
|
if (!merged) {
|
|
1760
1648
|
console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
|
|
1761
1649
|
return;
|
|
1762
1650
|
}
|
|
1763
1651
|
merged.boundsTree = new MeshBVH(merged);
|
|
1764
|
-
this.dynamicCollider = new
|
|
1765
|
-
merged,
|
|
1766
|
-
new THREE3.MeshBasicMaterial({
|
|
1767
|
-
opacity: 0.5,
|
|
1768
|
-
transparent: true,
|
|
1769
|
-
wireframe: true,
|
|
1770
|
-
depthTest: true
|
|
1771
|
-
})
|
|
1772
|
-
);
|
|
1652
|
+
this.dynamicCollider = new THREE4.Mesh(merged, new THREE4.MeshBasicMaterial({ opacity: 0.5, transparent: true, wireframe: true, depthTest: true }));
|
|
1773
1653
|
if (this.displayCollider) this.scene.add(this.dynamicCollider);
|
|
1774
1654
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
const dotProduct = normal.x * yAxis.x + normal.y * yAxis.y + normal.z * yAxis.z;
|
|
1778
|
-
const normalMagnitude = Math.sqrt(
|
|
1779
|
-
normal.x * normal.x + normal.y * normal.y + normal.z * normal.z
|
|
1780
|
-
);
|
|
1781
|
-
const cosTheta = dotProduct / normalMagnitude;
|
|
1782
|
-
return Math.acos(cosTheta);
|
|
1783
|
-
}
|
|
1784
|
-
// ==================== 设置控制器过渡 ====================
|
|
1655
|
+
// ==================== 控制器过渡 ====================
|
|
1656
|
+
// 车辆切换过渡
|
|
1785
1657
|
setControllerTransition() {
|
|
1786
1658
|
if (this.isChangeControllerTransitionTimer) {
|
|
1787
1659
|
clearTimeout(this.isChangeControllerTransitionTimer);
|
|
1788
1660
|
this.isChangeControllerTransitionTimer = null;
|
|
1789
1661
|
}
|
|
1790
|
-
|
|
1791
|
-
for (const v of this.vehicles) {
|
|
1792
|
-
vGroups.push(v.vehicleGroup);
|
|
1793
|
-
}
|
|
1662
|
+
const vGroups = this.vehicles.map((v) => v.vehicleGroup);
|
|
1794
1663
|
this.createDynamicBVH(vGroups);
|
|
1795
1664
|
this.isChangeControllerTransitionTimer = setTimeout(() => {
|
|
1796
1665
|
this.isChangeControllerTransitionTimer = null;
|
|
1797
|
-
|
|
1798
|
-
this.clearVehicleVelocity(v);
|
|
1799
|
-
}
|
|
1666
|
+
this.vehicles.forEach((v) => this.clearVehicleVelocity(v));
|
|
1800
1667
|
this.createDynamicBVH(vGroups);
|
|
1801
1668
|
}, 3e3);
|
|
1802
1669
|
}
|
|
1803
|
-
//
|
|
1670
|
+
// 清零车辆速度
|
|
1804
1671
|
clearVehicleVelocity(v) {
|
|
1805
1672
|
if (!v || !this.world || !this.RAPIER) return;
|
|
1806
1673
|
const { chassisBody, vehicleController } = v;
|
|
1807
1674
|
const ZERO = new this.RAPIER.Vector3(0, 0, 0);
|
|
1808
1675
|
chassisBody.setLinvel(ZERO, true);
|
|
1809
1676
|
chassisBody.setAngvel(ZERO, true);
|
|
1810
|
-
const BIG_BRAKE = 1e6;
|
|
1811
1677
|
for (let i = 0; i < 4; i++) {
|
|
1812
1678
|
vehicleController.setWheelEngineForce(i, 0);
|
|
1813
|
-
vehicleController.setWheelBrake(i,
|
|
1679
|
+
vehicleController.setWheelBrake(i, 1e6);
|
|
1814
1680
|
}
|
|
1815
1681
|
vehicleController.updateVehicle(1 / 60);
|
|
1816
1682
|
this.world.timestep = 1 / 60;
|
|
1817
1683
|
this.world.step();
|
|
1818
1684
|
chassisBody.setLinvel(ZERO, true);
|
|
1819
1685
|
chassisBody.setAngvel(ZERO, true);
|
|
1820
|
-
for (let i = 0; i < 4; i++)
|
|
1821
|
-
vehicleController.setWheelBrake(i, 0);
|
|
1822
|
-
}
|
|
1686
|
+
for (let i = 0; i < 4; i++) vehicleController.setWheelBrake(i, 0);
|
|
1823
1687
|
}
|
|
1824
1688
|
// ==================== 循环更新 ====================
|
|
1689
|
+
// 主循环更新
|
|
1825
1690
|
async update(delta = clock.getDelta()) {
|
|
1826
1691
|
if (!this.isupdate || !this.player || !this.collider) return;
|
|
1827
1692
|
delta = Math.min(delta, 1 / 40);
|
|
1828
|
-
if (this.controllerMode
|
|
1693
|
+
if (this.controllerMode === 1) {
|
|
1829
1694
|
this.updateVehicle(delta);
|
|
1830
1695
|
} else {
|
|
1831
1696
|
this.updatePlayer(delta);
|
|
1832
|
-
if (this.isChangeControllerTransitionTimer)
|
|
1833
|
-
this.updateVehicleInertia(delta);
|
|
1697
|
+
if (this.isChangeControllerTransitionTimer) this.updateVehicleInertia(delta);
|
|
1834
1698
|
}
|
|
1835
1699
|
}
|
|
1836
|
-
|
|
1837
|
-
* 更新当前驾驶的车辆
|
|
1838
|
-
*/
|
|
1700
|
+
// 车辆帧更新
|
|
1839
1701
|
updateVehicle(delta) {
|
|
1840
1702
|
const v = this.activeVehicle;
|
|
1841
1703
|
if (!v || !this.world) return;
|
|
1842
1704
|
const { vehicleController, chassisBody, vehicleGroup } = v;
|
|
1843
1705
|
const rotation = chassisBody.rotation();
|
|
1844
|
-
const quat = new
|
|
1845
|
-
|
|
1846
|
-
rotation.y,
|
|
1847
|
-
rotation.z,
|
|
1848
|
-
rotation.w
|
|
1849
|
-
);
|
|
1850
|
-
const forward = new THREE3.Vector3(1, 0, 0).applyQuaternion(quat);
|
|
1706
|
+
const quat = new THREE4.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w);
|
|
1707
|
+
const forward = new THREE4.Vector3(1, 0, 0).applyQuaternion(quat);
|
|
1851
1708
|
const slopeAngle = Math.asin(forward.y);
|
|
1852
|
-
|
|
1853
|
-
if (slopeAngle < -0.05 && this.fwdPressed) factor = -Math.sin(slopeAngle) * 10;
|
|
1709
|
+
const factor = slopeAngle < -0.05 && this.fwdPressed ? -Math.sin(slopeAngle) * 10 : 1;
|
|
1854
1710
|
const accelerateForce = this.vehicleParams.power.accelerateForce * v.speedMultiplier;
|
|
1855
1711
|
const maxSpeed = this.vehicleParams.power.maxSpeed * v.speedMultiplier;
|
|
1856
|
-
const engineForce = (Number(this.fwdPressed)
|
|
1857
|
-
vehicleController.setWheelEngineForce(
|
|
1858
|
-
vehicleController.setWheelEngineForce(1, engineForce);
|
|
1859
|
-
vehicleController.setWheelEngineForce(2, engineForce);
|
|
1860
|
-
vehicleController.setWheelEngineForce(3, engineForce);
|
|
1712
|
+
const engineForce = (Number(this.fwdPressed) - Number(this.bkdPressed)) * accelerateForce * factor;
|
|
1713
|
+
for (let i = 0; i < 4; i++) vehicleController.setWheelEngineForce(i, engineForce);
|
|
1861
1714
|
const wheelBrake = Number(this.spacePressed) * this.vehicleParams.power.brakeForce * delta;
|
|
1862
|
-
vehicleController.setWheelBrake(
|
|
1863
|
-
vehicleController.setWheelBrake(1, wheelBrake);
|
|
1864
|
-
vehicleController.setWheelBrake(2, wheelBrake);
|
|
1865
|
-
vehicleController.setWheelBrake(3, wheelBrake);
|
|
1715
|
+
for (let i = 0; i < 4; i++) vehicleController.setWheelBrake(i, wheelBrake);
|
|
1866
1716
|
const currentSteering = vehicleController.wheelSteering(0) || 0;
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
steerSpeed = this.vehicleParams.steering.steerReturnSpeed || 0.15;
|
|
1871
|
-
} else {
|
|
1872
|
-
steerSpeed = this.vehicleParams.steering.steerSpeed || 0.08;
|
|
1873
|
-
}
|
|
1874
|
-
const steerLerpFactor = 1 - Math.pow(1 - steerSpeed, delta);
|
|
1875
|
-
const targetSteering = this.vehicleParams.steering.maxSteerAngle * steerDirection;
|
|
1876
|
-
const steering = THREE3.MathUtils.lerp(
|
|
1877
|
-
currentSteering,
|
|
1878
|
-
targetSteering,
|
|
1879
|
-
steerLerpFactor
|
|
1880
|
-
);
|
|
1717
|
+
const steerDir = Number(this.lftPressed) - Number(this.rgtPressed);
|
|
1718
|
+
const steerSpeed = steerDir === 0 ? this.vehicleParams.steering.steerReturnSpeed : this.vehicleParams.steering.steerSpeed;
|
|
1719
|
+
const steering = THREE4.MathUtils.lerp(currentSteering, this.vehicleParams.steering.maxSteerAngle * steerDir, 1 - Math.pow(1 - steerSpeed, delta));
|
|
1881
1720
|
vehicleController.setWheelSteering(0, steering);
|
|
1882
1721
|
vehicleController.setWheelSteering(1, steering);
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
} else {
|
|
1887
|
-
vehicleController.setWheelSideFrictionStiffness(2, 2);
|
|
1888
|
-
vehicleController.setWheelSideFrictionStiffness(3, 2);
|
|
1889
|
-
}
|
|
1722
|
+
const driftFriction = (this.rgtPressed || this.lftPressed) && this.shiftPressed ? 0.5 : 2;
|
|
1723
|
+
vehicleController.setWheelSideFrictionStiffness(2, driftFriction);
|
|
1724
|
+
vehicleController.setWheelSideFrictionStiffness(3, driftFriction);
|
|
1890
1725
|
this.updateVehicleInertia(delta);
|
|
1891
1726
|
if (!this.isFirstPerson) {
|
|
1892
1727
|
const lookTarget = vehicleGroup.position.clone();
|
|
@@ -1895,99 +1730,49 @@ var PlayerController = class {
|
|
|
1895
1730
|
this.camera.position.add(lookTarget);
|
|
1896
1731
|
this.controls.update();
|
|
1897
1732
|
const velocity = chassisBody.linvel();
|
|
1898
|
-
const currentSpeed =
|
|
1899
|
-
velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z
|
|
1900
|
-
);
|
|
1733
|
+
const currentSpeed = new THREE4.Vector3(velocity.x, velocity.y, velocity.z).length();
|
|
1901
1734
|
const speedRatio = Math.min(currentSpeed / maxSpeed, 1);
|
|
1902
|
-
const
|
|
1903
|
-
const
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
);
|
|
1909
|
-
this.
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
const direction = this._personToCam.clone().normalize();
|
|
1915
|
-
const desiredDist = targetDistance;
|
|
1916
|
-
this._raycasterPersonToCam.set(origin, direction);
|
|
1917
|
-
this._raycasterPersonToCam.far = desiredDist;
|
|
1918
|
-
const intersects = this._raycasterPersonToCam.intersectObject(
|
|
1919
|
-
this.collider,
|
|
1920
|
-
false
|
|
1921
|
-
);
|
|
1922
|
-
if (intersects.length > 0) {
|
|
1923
|
-
const hit = intersects[0];
|
|
1924
|
-
const safeDist = Math.max(
|
|
1925
|
-
hit.distance - this._camEpsilon,
|
|
1926
|
-
this.minCamDistance
|
|
1927
|
-
);
|
|
1928
|
-
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
1929
|
-
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
1735
|
+
const baseDist = v.size.l * 0.8;
|
|
1736
|
+
const maxDist = v.size.l * 5;
|
|
1737
|
+
const desiredDist = THREE4.MathUtils.lerp(baseDist, maxDist, speedRatio);
|
|
1738
|
+
const minSafeDist = baseDist;
|
|
1739
|
+
this.personToCam.subVectors(this.camera.position, vehicleGroup.position);
|
|
1740
|
+
const direction = this.personToCam.clone().normalize();
|
|
1741
|
+
this.raycasterPersonToCam.set(vehicleGroup.position, direction);
|
|
1742
|
+
this.raycasterPersonToCam.far = desiredDist;
|
|
1743
|
+
const hits = this.raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1744
|
+
if (hits.length > 0) {
|
|
1745
|
+
const safeDist = Math.max(hits[0].distance - this.camEpsilon, minSafeDist);
|
|
1746
|
+
this.camera.position.lerp(vehicleGroup.position.clone().add(direction.clone().multiplyScalar(safeDist)), this.camCollisionLerp);
|
|
1930
1747
|
} else {
|
|
1931
|
-
this.
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
);
|
|
1936
|
-
let safeDist = desiredDist;
|
|
1937
|
-
if (intersectsMaxDis.length) {
|
|
1938
|
-
const hitMax = intersectsMaxDis[0];
|
|
1939
|
-
safeDist = Math.min(
|
|
1940
|
-
desiredDist,
|
|
1941
|
-
hitMax.distance - this._camEpsilon
|
|
1942
|
-
);
|
|
1943
|
-
}
|
|
1944
|
-
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
1945
|
-
this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
|
|
1748
|
+
this.raycasterPersonToCam.far = maxDist;
|
|
1749
|
+
const maxHits = this.raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1750
|
+
const safeDist = maxHits.length > 0 ? Math.min(desiredDist, maxHits[0].distance - this.camEpsilon) : desiredDist;
|
|
1751
|
+
this.camera.position.lerp(vehicleGroup.position.clone().add(direction.clone().multiplyScalar(safeDist)), this.camCollisionLerp);
|
|
1946
1752
|
}
|
|
1947
1753
|
if ((this.fwdPressed || this.bkdPressed) && this.vehicleParams.followVehicleDirection) {
|
|
1948
1754
|
const vel = chassisBody.linvel();
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
const
|
|
1953
|
-
this.
|
|
1954
|
-
const camHeightOffset = v.size.h;
|
|
1955
|
-
const targetCamPos = lookTarget.clone().add(
|
|
1956
|
-
this.camBehindDir.clone().multiplyScalar(desiredDist)
|
|
1957
|
-
).add(new THREE3.Vector3(0, camHeightOffset, 0));
|
|
1958
|
-
this.camera.position.lerp(
|
|
1959
|
-
targetCamPos,
|
|
1960
|
-
this._camCollisionLerp
|
|
1961
|
-
);
|
|
1755
|
+
const velVec = new THREE4.Vector3(vel.x, vel.y, vel.z);
|
|
1756
|
+
if (velVec.length() > 0.3) {
|
|
1757
|
+
this.camBehindDir.lerp(velVec.normalize().negate(), this.camCollisionLerp).normalize();
|
|
1758
|
+
const targetCamPos = lookTarget.clone().add(this.camBehindDir.clone().multiplyScalar(desiredDist)).add(new THREE4.Vector3(0, v.size.h, 0));
|
|
1759
|
+
this.camera.position.lerp(targetCamPos, this.camCollisionLerp);
|
|
1962
1760
|
this.controls.update();
|
|
1963
1761
|
}
|
|
1964
1762
|
}
|
|
1965
1763
|
}
|
|
1966
1764
|
const vehicleUp = this.upVector.clone().applyQuaternion(vehicleGroup.quaternion);
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
const size = new THREE3.Vector3();
|
|
1765
|
+
if (vehicleUp.angleTo(this.upVector) > Math.PI / 2) {
|
|
1766
|
+
const size = new THREE4.Vector3();
|
|
1970
1767
|
v.vehicleBBox?.getSize(size);
|
|
1971
|
-
const
|
|
1972
|
-
chassisBody.setTranslation(
|
|
1973
|
-
|
|
1974
|
-
translation2.x,
|
|
1975
|
-
translation2.y + size.y,
|
|
1976
|
-
translation2.z
|
|
1977
|
-
),
|
|
1978
|
-
true
|
|
1979
|
-
);
|
|
1980
|
-
chassisBody.setRotation(
|
|
1981
|
-
new this.RAPIER.Quaternion(0, 0, 0, 1),
|
|
1982
|
-
true
|
|
1983
|
-
);
|
|
1768
|
+
const t = chassisBody.translation();
|
|
1769
|
+
chassisBody.setTranslation(new this.RAPIER.Vector3(t.x, t.y + size.y, t.z), true);
|
|
1770
|
+
chassisBody.setRotation(new this.RAPIER.Quaternion(0, 0, 0, 1), true);
|
|
1984
1771
|
chassisBody.setLinvel(new this.RAPIER.Vector3(0, 0, 0), true);
|
|
1985
1772
|
chassisBody.setAngvel(new this.RAPIER.Vector3(0, 0, 0), true);
|
|
1986
1773
|
}
|
|
1987
1774
|
}
|
|
1988
|
-
|
|
1989
|
-
* 更新所有车辆物理和位置
|
|
1990
|
-
*/
|
|
1775
|
+
// 物理步进 & 同步
|
|
1991
1776
|
updateVehicleInertia(delta) {
|
|
1992
1777
|
if (!this.world) return;
|
|
1993
1778
|
this.world.timestep = delta;
|
|
@@ -1996,41 +1781,21 @@ var PlayerController = class {
|
|
|
1996
1781
|
const { vehicleController, chassisBody, vehicleGroup, updateWheelVisuals } = v;
|
|
1997
1782
|
vehicleController.updateVehicle(delta);
|
|
1998
1783
|
if (chassisBody.isSleeping()) continue;
|
|
1999
|
-
const
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2002
|
-
)
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const s = maxSpeed / currentSpeed;
|
|
2006
|
-
chassisBody.setLinvel(
|
|
2007
|
-
new this.RAPIER.Vector3(
|
|
2008
|
-
velocity.x * s,
|
|
2009
|
-
velocity.y * s,
|
|
2010
|
-
velocity.z * s
|
|
2011
|
-
),
|
|
2012
|
-
true
|
|
2013
|
-
);
|
|
1784
|
+
const vel = chassisBody.linvel();
|
|
1785
|
+
const speed = new THREE4.Vector3(vel.x, vel.y, vel.z).length();
|
|
1786
|
+
const max = this.vehicleParams.power.maxSpeed * v.speedMultiplier;
|
|
1787
|
+
if (speed > max) {
|
|
1788
|
+
const s = max / speed;
|
|
1789
|
+
chassisBody.setLinvel(new this.RAPIER.Vector3(vel.x * s, vel.y * s, vel.z * s), true);
|
|
2014
1790
|
}
|
|
2015
|
-
const
|
|
2016
|
-
const
|
|
2017
|
-
vehicleGroup.position.set(
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
translation.z
|
|
2021
|
-
);
|
|
2022
|
-
vehicleGroup.quaternion.set(
|
|
2023
|
-
rotationSync.x,
|
|
2024
|
-
rotationSync.y,
|
|
2025
|
-
rotationSync.z,
|
|
2026
|
-
rotationSync.w
|
|
2027
|
-
);
|
|
2028
|
-
if (updateWheelVisuals) updateWheelVisuals();
|
|
1791
|
+
const t = chassisBody.translation();
|
|
1792
|
+
const r = chassisBody.rotation();
|
|
1793
|
+
vehicleGroup.position.set(t.x, t.y, t.z);
|
|
1794
|
+
vehicleGroup.quaternion.set(r.x, r.y, r.z, r.w);
|
|
1795
|
+
updateWheelVisuals?.();
|
|
2029
1796
|
}
|
|
2030
1797
|
}
|
|
2031
|
-
|
|
2032
|
-
* 设置人物缩放
|
|
2033
|
-
*/
|
|
1798
|
+
// 缩放玩家比例
|
|
2034
1799
|
setPlayerScale(newScale) {
|
|
2035
1800
|
if (newScale <= 0) return;
|
|
2036
1801
|
const ratio = newScale / this.playerModel.scale;
|
|
@@ -2040,7 +1805,7 @@ var PlayerController = class {
|
|
|
2040
1805
|
this.playerSpeed *= ratio;
|
|
2041
1806
|
this.playerFlySpeed *= ratio;
|
|
2042
1807
|
this.curPlayerSpeed *= ratio;
|
|
2043
|
-
this.
|
|
1808
|
+
this.camEpsilon *= ratio;
|
|
2044
1809
|
this.minCamDistance *= ratio;
|
|
2045
1810
|
this.maxCamDistance *= ratio;
|
|
2046
1811
|
this.orginMaxCamDistance *= ratio;
|
|
@@ -2049,38 +1814,32 @@ var PlayerController = class {
|
|
|
2049
1814
|
if (this.player?.capsuleInfo) this.player.capsuleInfo.radius *= ratio;
|
|
2050
1815
|
if (this.isFirstPerson) this.setFirstPersonCamera();
|
|
2051
1816
|
}
|
|
2052
|
-
|
|
2053
|
-
* 更新人物
|
|
2054
|
-
*/
|
|
1817
|
+
// 玩家帧更新
|
|
2055
1818
|
updatePlayer(delta) {
|
|
2056
|
-
if (this.isMovingToBoardingPoint)
|
|
2057
|
-
|
|
2058
|
-
}
|
|
2059
|
-
if (!this.isFlying) {
|
|
2060
|
-
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
2061
|
-
}
|
|
1819
|
+
if (this.isMovingToBoardingPoint) this.updateMoveToBoardingPoint(delta);
|
|
1820
|
+
if (!this.isFlying) this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
2062
1821
|
if (this.isBoardingAnimPlaying) {
|
|
2063
1822
|
const action = this.personActions?.get("enterCar");
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
1823
|
+
if (action) {
|
|
1824
|
+
const duration = action.getClip().duration;
|
|
1825
|
+
const remaining = (duration - action.time) / action.getEffectiveTimeScale() * 1e3;
|
|
1826
|
+
if (!this.closeDoorTriggered && remaining <= 500) {
|
|
1827
|
+
this.closeDoorTriggered = true;
|
|
1828
|
+
this.openVehicleDoor(false);
|
|
1829
|
+
}
|
|
1830
|
+
if (action.time >= duration) {
|
|
1831
|
+
this.isBoardingAnimPlaying = false;
|
|
1832
|
+
this.closeDoorTriggered = false;
|
|
1833
|
+
this.onEnterCarAnimFinished();
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
2076
1836
|
}
|
|
2077
1837
|
}
|
|
2078
1838
|
if (this.isExitAnimPlaying) {
|
|
2079
1839
|
const action = this.personActions?.get("exitCar");
|
|
2080
1840
|
if (action) {
|
|
2081
1841
|
const duration = action.getClip().duration;
|
|
2082
|
-
const
|
|
2083
|
-
const remaining = (duration - action.time) / timeScale * 1e3;
|
|
1842
|
+
const remaining = (duration - action.time) / action.getEffectiveTimeScale() * 1e3;
|
|
2084
1843
|
if (!this.closeExitDoorTriggered && remaining <= 500) {
|
|
2085
1844
|
this.closeExitDoorTriggered = true;
|
|
2086
1845
|
this.openVehicleDoor(false);
|
|
@@ -2094,71 +1853,47 @@ var PlayerController = class {
|
|
|
2094
1853
|
this.updateMixers(delta);
|
|
2095
1854
|
if (this.controllerMode === 1) return;
|
|
2096
1855
|
this.camera.getWorldDirection(this.camDir);
|
|
2097
|
-
|
|
2098
|
-
angle = 2 * Math.PI - angle;
|
|
1856
|
+
const angle = 2 * Math.PI - (Math.atan2(this.camDir.z, this.camDir.x) + Math.PI / 2);
|
|
2099
1857
|
this.moveDir.set(0, 0, 0);
|
|
2100
1858
|
if (this.fwdPressed) this.moveDir.add(this.DIR_FWD);
|
|
2101
1859
|
if (this.bkdPressed) this.moveDir.add(this.DIR_BKD);
|
|
2102
1860
|
if (this.lftPressed) this.moveDir.add(this.DIR_LFT);
|
|
2103
1861
|
if (this.rgtPressed) this.moveDir.add(this.DIR_RGT);
|
|
2104
1862
|
if (this.isFlying) {
|
|
2105
|
-
|
|
2106
|
-
else this.moveDir.y = 0;
|
|
1863
|
+
this.moveDir.y = this.fwdPressed ? this.camDir.y : 0;
|
|
2107
1864
|
if (this.spacePressed) this.moveDir.add(this.DIR_UP);
|
|
2108
|
-
}
|
|
2109
|
-
if (this.isFlying && this.fwdPressed) {
|
|
2110
1865
|
this.curPlayerSpeed = this.shiftPressed ? this.playerFlySpeed * 2 : this.playerFlySpeed;
|
|
2111
1866
|
} else {
|
|
2112
1867
|
this.curPlayerSpeed = this.shiftPressed ? this.playerSpeed * 2 : this.playerSpeed;
|
|
2113
1868
|
}
|
|
2114
1869
|
this.moveDir.normalize().applyAxisAngle(this.upVector, angle);
|
|
2115
|
-
this.player.position.addScaledVector(
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
)
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
this.
|
|
2122
|
-
this.
|
|
2123
|
-
this.
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
if (intersects.length > 0) {
|
|
2131
|
-
playerDistanceFromGround = this.player.position.y - intersects[0].point.y;
|
|
2132
|
-
const maxH = this.playerCapsuleHeight * this.playerModel.scale * 0.9;
|
|
2133
|
-
const h = this.playerCapsuleHeight * this.playerModel.scale * 0.75;
|
|
2134
|
-
const minH = this.playerCapsuleHeight * this.playerModel.scale * 0.7;
|
|
2135
|
-
if (!this.isFlying) {
|
|
2136
|
-
if (playerDistanceFromGround >= maxH) {
|
|
2137
|
-
this.playerVelocity.y += delta * this.gravity;
|
|
2138
|
-
this.player.position.addScaledVector(
|
|
2139
|
-
this.playerVelocity,
|
|
2140
|
-
delta
|
|
2141
|
-
);
|
|
2142
|
-
this.playerIsOnGround = false;
|
|
2143
|
-
} else if (playerDistanceFromGround >= h && playerDistanceFromGround < maxH) {
|
|
2144
|
-
if (!this.spacePressed) {
|
|
2145
|
-
this.playerVelocity.set(0, 0, 0);
|
|
2146
|
-
this.playerIsOnGround = true;
|
|
2147
|
-
this.player.position.y = intersects[0].point.y + h;
|
|
2148
|
-
}
|
|
2149
|
-
} else if (playerDistanceFromGround >= minH && playerDistanceFromGround < h) {
|
|
2150
|
-
this.playerVelocity.set(0, 0, 0);
|
|
2151
|
-
this.playerIsOnGround = true;
|
|
2152
|
-
this.player.position.y = intersects[0].point.y + h;
|
|
2153
|
-
} else if (playerDistanceFromGround < minH) {
|
|
1870
|
+
this.player.position.addScaledVector(this.moveDir, this.curPlayerSpeed * delta);
|
|
1871
|
+
this.raycaster.ray.origin.copy(this.player.position);
|
|
1872
|
+
const hits = this.raycaster.intersectObject(this.collider, false);
|
|
1873
|
+
if (hits.length > 0 && !this.isFlying) {
|
|
1874
|
+
const dist = this.player.position.y - hits[0].point.y;
|
|
1875
|
+
const s = this.playerModel.scale;
|
|
1876
|
+
const maxH = this.playerCapsuleHeight * s * 0.9;
|
|
1877
|
+
const h = this.playerCapsuleHeight * s * 0.75;
|
|
1878
|
+
const minH = this.playerCapsuleHeight * s * 0.7;
|
|
1879
|
+
if (dist >= maxH) {
|
|
1880
|
+
this.playerVelocity.y += delta * this.gravity;
|
|
1881
|
+
this.player.position.addScaledVector(this.playerVelocity, delta);
|
|
1882
|
+
this.playerIsOnGround = false;
|
|
1883
|
+
} else if (dist >= h && dist < maxH) {
|
|
1884
|
+
if (!this.spacePressed) {
|
|
2154
1885
|
this.playerVelocity.set(0, 0, 0);
|
|
2155
|
-
this.player.position.set(
|
|
2156
|
-
this.player.position.x,
|
|
2157
|
-
intersects[0].point.y + h,
|
|
2158
|
-
this.player.position.z
|
|
2159
|
-
);
|
|
2160
1886
|
this.playerIsOnGround = true;
|
|
1887
|
+
this.player.position.y = hits[0].point.y + h;
|
|
2161
1888
|
}
|
|
1889
|
+
} else if (dist >= minH) {
|
|
1890
|
+
this.playerVelocity.set(0, 0, 0);
|
|
1891
|
+
this.playerIsOnGround = true;
|
|
1892
|
+
this.player.position.y = hits[0].point.y + h;
|
|
1893
|
+
} else {
|
|
1894
|
+
this.playerVelocity.set(0, 0, 0);
|
|
1895
|
+
this.player.position.y = hits[0].point.y + h;
|
|
1896
|
+
this.playerIsOnGround = true;
|
|
2162
1897
|
}
|
|
2163
1898
|
}
|
|
2164
1899
|
this.player.updateMatrixWorld();
|
|
@@ -2168,113 +1903,49 @@ var PlayerController = class {
|
|
|
2168
1903
|
this.tempSegment.copy(capsuleInfo.segment);
|
|
2169
1904
|
this.tempSegment.start.applyMatrix4(this.player.matrixWorld).applyMatrix4(this.tempMat);
|
|
2170
1905
|
this.tempSegment.end.applyMatrix4(this.player.matrixWorld).applyMatrix4(this.tempMat);
|
|
2171
|
-
this.tempBox.expandByPoint(this.tempSegment.start);
|
|
2172
|
-
this.tempBox.expandByPoint(this.tempSegment.end);
|
|
1906
|
+
this.tempBox.expandByPoint(this.tempSegment.start).expandByPoint(this.tempSegment.end);
|
|
2173
1907
|
this.tempBox.expandByScalar(capsuleInfo.radius);
|
|
2174
1908
|
if (!this.isMovingToBoardingPoint) {
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
const direction = capsulePoint.sub(triPoint).normalize();
|
|
2190
|
-
this.tempSegment.start.addScaledVector(
|
|
2191
|
-
direction,
|
|
2192
|
-
depth
|
|
2193
|
-
);
|
|
2194
|
-
this.tempSegment.end.addScaledVector(direction, depth);
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
});
|
|
2198
|
-
this.dynamicCollider?.geometry?.boundsTree?.shapecast({
|
|
2199
|
-
intersectsBounds: (box) => box.intersectsBox(this.tempBox),
|
|
2200
|
-
intersectsTriangle: (tri) => {
|
|
2201
|
-
const triPoint = this.tempVector;
|
|
2202
|
-
const capsulePoint = this.tempVector2;
|
|
2203
|
-
const distance = tri.closestPointToSegment(
|
|
2204
|
-
this.tempSegment,
|
|
2205
|
-
triPoint,
|
|
2206
|
-
capsulePoint
|
|
2207
|
-
);
|
|
2208
|
-
if (distance < capsuleInfo.radius) {
|
|
2209
|
-
const depth = capsuleInfo.radius - distance;
|
|
2210
|
-
const direction = capsulePoint.sub(triPoint).normalize();
|
|
2211
|
-
this.tempSegment.start.addScaledVector(
|
|
2212
|
-
direction,
|
|
2213
|
-
depth
|
|
2214
|
-
);
|
|
2215
|
-
this.tempSegment.end.addScaledVector(direction, depth);
|
|
1909
|
+
const resolveCollision = (collider) => {
|
|
1910
|
+
if (!collider) return;
|
|
1911
|
+
collider.geometry?.boundsTree?.shapecast({
|
|
1912
|
+
intersectsBounds: (box) => box.intersectsBox(this.tempBox),
|
|
1913
|
+
intersectsTriangle: (tri) => {
|
|
1914
|
+
const distance = tri.closestPointToSegment(this.tempSegment, this.tempVector, this.tempVector2);
|
|
1915
|
+
if (distance < capsuleInfo.radius) {
|
|
1916
|
+
const normal = tri.getNormal(new THREE4.Vector3());
|
|
1917
|
+
if (normal.y > 0.5 && !this.isFlying) return;
|
|
1918
|
+
const dir = this.tempVector2.sub(this.tempVector).normalize();
|
|
1919
|
+
const depth = capsuleInfo.radius - distance;
|
|
1920
|
+
this.tempSegment.start.addScaledVector(dir, depth);
|
|
1921
|
+
this.tempSegment.end.addScaledVector(dir, depth);
|
|
1922
|
+
}
|
|
2216
1923
|
}
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
1924
|
+
});
|
|
1925
|
+
};
|
|
1926
|
+
resolveCollision(this.collider);
|
|
1927
|
+
resolveCollision(this.dynamicCollider);
|
|
2219
1928
|
}
|
|
2220
|
-
const
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
)
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
if (this.thirdMouseMode === 0 || this.thirdMouseMode === 2) {
|
|
2236
|
-
if (this.moveDir.lengthSq() > 0) {
|
|
2237
|
-
lookTarget = this.player.position.clone().add(this.moveDir);
|
|
2238
|
-
} else {
|
|
2239
|
-
lookTarget = this.player.position.clone().add(this.camDir);
|
|
1929
|
+
const newPos = this.tempVector.copy(this.tempSegment.start).applyMatrix4(this.collider.matrixWorld);
|
|
1930
|
+
const deltaVec = this.tempVector2.subVectors(newPos, this.player.position);
|
|
1931
|
+
const offset = Math.max(0, deltaVec.length() - 1e-5);
|
|
1932
|
+
this.player.position.add(deltaVec.normalize().multiplyScalar(offset));
|
|
1933
|
+
if (!this.isFirstPerson) {
|
|
1934
|
+
const camDirFlat = this.camDir.clone().setY(0).normalize().negate();
|
|
1935
|
+
const moveDirFlat = this.moveDir.clone().normalize().negate();
|
|
1936
|
+
if (!this.isFlying) {
|
|
1937
|
+
if (this.thirdMouseMode === 0 || this.thirdMouseMode === 2) {
|
|
1938
|
+
const lookTarget = this.player.position.clone().add(moveDirFlat.lengthSq() > 0 ? moveDirFlat : camDirFlat);
|
|
1939
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
1940
|
+
this.player.quaternion.slerp(this.targetQuat.setFromRotationMatrix(this.targetMat), Math.min(1, this.rotationSpeed * delta));
|
|
1941
|
+
} else if (moveDirFlat.lengthSq() > 0) {
|
|
1942
|
+
this.targetMat.lookAt(this.player.position, this.player.position.clone().add(moveDirFlat), this.player.up);
|
|
1943
|
+
this.player.quaternion.slerp(this.targetQuat.setFromRotationMatrix(this.targetMat), Math.min(1, this.rotationSpeed * delta));
|
|
2240
1944
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
);
|
|
2246
|
-
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
2247
|
-
const alpha = Math.min(1, this.rotationSpeed * delta);
|
|
2248
|
-
this.player.quaternion.slerp(this.targetQuat, alpha);
|
|
2249
|
-
}
|
|
2250
|
-
if ((this.thirdMouseMode === 1 || this.thirdMouseMode === 3) && this.moveDir.lengthSq() > 0) {
|
|
2251
|
-
lookTarget = this.player.position.clone().add(this.moveDir);
|
|
2252
|
-
this.targetMat.lookAt(
|
|
2253
|
-
this.player.position,
|
|
2254
|
-
lookTarget,
|
|
2255
|
-
this.player.up
|
|
2256
|
-
);
|
|
2257
|
-
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
2258
|
-
const alpha = Math.min(1, this.rotationSpeed * delta);
|
|
2259
|
-
this.player.quaternion.slerp(this.targetQuat, alpha);
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
if (this.isFlying) {
|
|
2263
|
-
if (!this.isFirstPerson) {
|
|
2264
|
-
this.camDir.y = 0;
|
|
2265
|
-
this.camDir.normalize();
|
|
2266
|
-
this.camDir.negate();
|
|
2267
|
-
this.moveDir.normalize();
|
|
2268
|
-
this.moveDir.negate();
|
|
2269
|
-
const lookTarget = this.player.position.clone().add(this.fwdPressed ? this.moveDir : this.camDir);
|
|
2270
|
-
this.targetMat.lookAt(
|
|
2271
|
-
this.player.position,
|
|
2272
|
-
lookTarget,
|
|
2273
|
-
this.player.up
|
|
2274
|
-
);
|
|
2275
|
-
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
2276
|
-
const alpha = Math.min(1, this.rotationSpeed * delta);
|
|
2277
|
-
this.player.quaternion.slerp(this.targetQuat, alpha);
|
|
1945
|
+
} else {
|
|
1946
|
+
const lookTarget = this.player.position.clone().add(this.fwdPressed ? moveDirFlat : camDirFlat);
|
|
1947
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
1948
|
+
this.player.quaternion.slerp(this.targetQuat.setFromRotationMatrix(this.targetMat), Math.min(1, this.rotationSpeed * delta));
|
|
2278
1949
|
}
|
|
2279
1950
|
}
|
|
2280
1951
|
if (!this.isFirstPerson) {
|
|
@@ -2285,131 +1956,84 @@ var PlayerController = class {
|
|
|
2285
1956
|
this.camera.position.add(lookTarget);
|
|
2286
1957
|
this.controls.update();
|
|
2287
1958
|
if (!this.enableZoom) {
|
|
2288
|
-
this.
|
|
2289
|
-
this.
|
|
2290
|
-
this.player.position
|
|
2291
|
-
|
|
2292
|
-
const origin = this.player.position.clone();
|
|
2293
|
-
const direction = this._personToCam.clone().normalize();
|
|
2294
|
-
const desiredDist = this._personToCam.length();
|
|
2295
|
-
this._raycasterPersonToCam.set(origin, direction);
|
|
2296
|
-
this._raycasterPersonToCam.far = desiredDist;
|
|
2297
|
-
const intersectsCamera = this._raycasterPersonToCam.intersectObject(
|
|
2298
|
-
this.collider,
|
|
2299
|
-
false
|
|
1959
|
+
this.updateCameraWithRaycast(
|
|
1960
|
+
this.player.position,
|
|
1961
|
+
this.personToCam.subVectors(this.camera.position, this.player.position).length(),
|
|
1962
|
+
this.maxCamDistance
|
|
2300
1963
|
);
|
|
2301
|
-
if (intersectsCamera.length > 0) {
|
|
2302
|
-
const hit = intersectsCamera[0];
|
|
2303
|
-
const safeDist = Math.max(
|
|
2304
|
-
hit.distance - this._camEpsilon,
|
|
2305
|
-
this.minCamDistance
|
|
2306
|
-
);
|
|
2307
|
-
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
2308
|
-
this.camera.position.lerp(
|
|
2309
|
-
targetCamPos,
|
|
2310
|
-
this._camCollisionLerp
|
|
2311
|
-
);
|
|
2312
|
-
} else {
|
|
2313
|
-
this._raycasterPersonToCam.far = this.maxCamDistance;
|
|
2314
|
-
const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(
|
|
2315
|
-
this.collider,
|
|
2316
|
-
false
|
|
2317
|
-
);
|
|
2318
|
-
let safeDist = this.maxCamDistance;
|
|
2319
|
-
if (intersectsMaxDis.length) {
|
|
2320
|
-
const hitMax = intersectsMaxDis[0];
|
|
2321
|
-
safeDist = hitMax.distance - this._camEpsilon;
|
|
2322
|
-
}
|
|
2323
|
-
const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
|
|
2324
|
-
this.camera.position.lerp(
|
|
2325
|
-
targetCamPos,
|
|
2326
|
-
this._camCollisionLerp
|
|
2327
|
-
);
|
|
2328
|
-
}
|
|
2329
1964
|
}
|
|
2330
1965
|
}
|
|
2331
1966
|
if (this.player.position.y < this.boundingBoxMinY - 1) {
|
|
2332
|
-
this.
|
|
1967
|
+
this.raycaster.ray.origin.set(this.player.position.x, 1e4, this.player.position.z);
|
|
1968
|
+
const fallHits = this.raycaster.intersectObject(this.collider, false);
|
|
1969
|
+
this.reset(new THREE4.Vector3(
|
|
2333
1970
|
this.player.position.x,
|
|
2334
|
-
|
|
1971
|
+
fallHits.length > 0 ? fallHits[0].point.y + 5 : this.player.position.y + 15,
|
|
2335
1972
|
this.player.position.z
|
|
2336
|
-
);
|
|
2337
|
-
this._raycaster.ray.origin.copy(this._originTmp);
|
|
2338
|
-
const intersectsFall = this._raycaster.intersectObject(
|
|
2339
|
-
this.collider,
|
|
2340
|
-
false
|
|
2341
|
-
);
|
|
2342
|
-
if (intersectsFall.length > 0) {
|
|
2343
|
-
console.log("\u73A9\u5BB6\u4E3Abug\u610F\u5916\u6389\u843D");
|
|
2344
|
-
this.reset(
|
|
2345
|
-
new THREE3.Vector3(
|
|
2346
|
-
this.player.position.x,
|
|
2347
|
-
intersectsFall[0].point.y + 5,
|
|
2348
|
-
this.player.position.z
|
|
2349
|
-
)
|
|
2350
|
-
);
|
|
2351
|
-
} else {
|
|
2352
|
-
console.log("\u73A9\u5BB6\u6B63\u5E38\u6389\u843D");
|
|
2353
|
-
this.reset(
|
|
2354
|
-
new THREE3.Vector3(
|
|
2355
|
-
this.player.position.x,
|
|
2356
|
-
this.player.position.y + 15,
|
|
2357
|
-
this.player.position.z
|
|
2358
|
-
)
|
|
2359
|
-
);
|
|
2360
|
-
}
|
|
1973
|
+
));
|
|
2361
1974
|
}
|
|
2362
|
-
if (this.isShowMobileControls) {
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
if (this.player.position.distanceTo(this.nearCheckWorld) < 800 * this.playerModel.scale) {
|
|
2371
|
-
near = true;
|
|
2372
|
-
this.syncVehicleBtnEl(near);
|
|
2373
|
-
break;
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
if (near !== this.isNearVehicle) {
|
|
2377
|
-
this.isNearVehicle = near;
|
|
2378
|
-
this.syncVehicleBtnEl(near);
|
|
1975
|
+
if (this.isShowMobileControls && this.vehicles.length) {
|
|
1976
|
+
let near = false;
|
|
1977
|
+
for (const v of this.vehicles) {
|
|
1978
|
+
this.nearCheckLocal.copy(v.boardingPoint).multiplyScalar(v.scale);
|
|
1979
|
+
v.vehicleGroup.localToWorld(this.nearCheckWorld.copy(this.nearCheckLocal));
|
|
1980
|
+
if (this.player.position.distanceTo(this.nearCheckWorld) < 800 * this.playerModel.scale) {
|
|
1981
|
+
near = true;
|
|
1982
|
+
break;
|
|
2379
1983
|
}
|
|
2380
|
-
} else {
|
|
2381
|
-
this.isNearVehicle = false;
|
|
2382
|
-
this.syncVehicleBtnEl(false);
|
|
2383
1984
|
}
|
|
1985
|
+
if (near !== this.isNearVehicle) {
|
|
1986
|
+
this.isNearVehicle = near;
|
|
1987
|
+
this.mobileControls?.syncVehicleBtn(near);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
// 相机碰撞射线
|
|
1992
|
+
updateCameraWithRaycast(origin, desiredDist, maxDist) {
|
|
1993
|
+
this.personToCam.subVectors(this.camera.position, origin);
|
|
1994
|
+
const direction = this.personToCam.clone().normalize();
|
|
1995
|
+
this.raycasterPersonToCam.set(origin, direction);
|
|
1996
|
+
this.raycasterPersonToCam.far = desiredDist;
|
|
1997
|
+
const hits = this.raycasterPersonToCam.intersectObject(this.collider, false);
|
|
1998
|
+
if (hits.length > 0) {
|
|
1999
|
+
const safeDist = Math.max(hits[0].distance - this.camEpsilon, this.minCamDistance);
|
|
2000
|
+
const targetCamPos = origin.clone().add(direction.multiplyScalar(safeDist));
|
|
2001
|
+
this.camera.position.lerp(targetCamPos, this.camCollisionLerp);
|
|
2002
|
+
} else {
|
|
2003
|
+
this.raycasterPersonToCam.far = maxDist;
|
|
2004
|
+
const maxHits = this.raycasterPersonToCam.intersectObject(this.collider, false);
|
|
2005
|
+
const safeDist = maxHits.length > 0 ? Math.min(maxDist, maxHits[0].distance - this.camEpsilon) : maxDist;
|
|
2006
|
+
const targetCamPos = origin.clone().add(direction.multiplyScalar(safeDist));
|
|
2007
|
+
this.camera.position.lerp(targetCamPos, this.camCollisionLerp);
|
|
2384
2008
|
}
|
|
2385
2009
|
}
|
|
2386
|
-
|
|
2387
|
-
* 获取屏幕中心点向前射线与碰撞体的交点
|
|
2388
|
-
*/
|
|
2010
|
+
// 屏幕中心射线
|
|
2389
2011
|
getCenterScreenRaycastHit() {
|
|
2390
2012
|
this.camera.updateMatrixWorld();
|
|
2391
2013
|
this.centerRay.setFromCamera(this.centerMouse, this.camera);
|
|
2392
|
-
|
|
2393
|
-
return intersects[0];
|
|
2014
|
+
return this.centerRay.intersectObject(this.collider, false)[0];
|
|
2394
2015
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2016
|
+
// 获取当前人物动画名称
|
|
2017
|
+
getCurrentPersonAnimationName() {
|
|
2018
|
+
return this.actionState._clip.name;
|
|
2019
|
+
}
|
|
2020
|
+
// 更新动画混合器
|
|
2398
2021
|
updateMixers(delta) {
|
|
2399
|
-
|
|
2400
|
-
for (const v of this.vehicles)
|
|
2401
|
-
v.vehicleMixer?.update(delta);
|
|
2402
|
-
}
|
|
2022
|
+
this.personMixer?.update(delta);
|
|
2023
|
+
for (const v of this.vehicles) v.vehicleMixer?.update(delta);
|
|
2403
2024
|
}
|
|
2025
|
+
// 重置玩家位置
|
|
2404
2026
|
reset(position) {
|
|
2405
2027
|
if (!this.player) return;
|
|
2406
2028
|
this.playerVelocity.set(0, 0, 0);
|
|
2407
2029
|
this.player.position.copy(position ?? this.initPos);
|
|
2408
2030
|
}
|
|
2031
|
+
// 获取玩家位置
|
|
2409
2032
|
getPosition() {
|
|
2410
2033
|
return this.player?.position;
|
|
2411
2034
|
}
|
|
2412
2035
|
// ==================== 输入处理 ====================
|
|
2036
|
+
// 外部输入接口
|
|
2413
2037
|
setInput(input) {
|
|
2414
2038
|
if (typeof input.moveX === "number") {
|
|
2415
2039
|
this.lftPressed = input.moveX === -1;
|
|
@@ -2426,14 +2050,9 @@ var PlayerController = class {
|
|
|
2426
2050
|
}
|
|
2427
2051
|
if (typeof input.jump === "boolean") {
|
|
2428
2052
|
if (input.jump) {
|
|
2429
|
-
|
|
2430
|
-
this.isMovingToBoardingPoint = false;
|
|
2431
|
-
this.boardingWaypoints = [];
|
|
2432
|
-
this.currentWaypointIndex = 0;
|
|
2433
|
-
this.boardingTargetDir = null;
|
|
2434
|
-
}
|
|
2053
|
+
this.cancelBoarding();
|
|
2435
2054
|
this.spacePressed = true;
|
|
2436
|
-
if (this.controllerMode
|
|
2055
|
+
if (this.controllerMode === 1) return;
|
|
2437
2056
|
if (!this.playerIsOnGround || this.isFlying) return;
|
|
2438
2057
|
this.playPersonAnimationByName("jumping");
|
|
2439
2058
|
this.playerVelocity.y = this.jumpHeight;
|
|
@@ -2442,344 +2061,91 @@ var PlayerController = class {
|
|
|
2442
2061
|
this.spacePressed = false;
|
|
2443
2062
|
}
|
|
2444
2063
|
}
|
|
2445
|
-
if (typeof input.shift === "boolean")
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
if (input.toggleView) {
|
|
2449
|
-
this.changeView();
|
|
2450
|
-
}
|
|
2451
|
-
if (input.toggleFly && this.playerFlyEnabled && this.controllerMode == 0) {
|
|
2064
|
+
if (typeof input.shift === "boolean") this.shiftPressed = input.shift;
|
|
2065
|
+
if (input.toggleView) this.changeView();
|
|
2066
|
+
if (input.toggleFly && this.playerFlyEnabled && this.controllerMode === 0) {
|
|
2452
2067
|
this.isFlying = !this.isFlying;
|
|
2453
2068
|
this.setAnimationByPressed();
|
|
2454
|
-
if (!this.isFlying && !this.playerIsOnGround)
|
|
2455
|
-
this.playPersonAnimationByName("jumping");
|
|
2456
|
-
}
|
|
2069
|
+
if (!this.isFlying && !this.playerIsOnGround) this.playPersonAnimationByName("jumping");
|
|
2457
2070
|
}
|
|
2458
2071
|
if (input.toggleVehicle) {
|
|
2459
|
-
if (this.controllerMode
|
|
2460
|
-
|
|
2461
|
-
} else {
|
|
2462
|
-
this.exitVehicle();
|
|
2463
|
-
}
|
|
2072
|
+
if (this.controllerMode === 0) this.enterVehicle();
|
|
2073
|
+
else this.exitVehicle();
|
|
2464
2074
|
}
|
|
2465
2075
|
}
|
|
2076
|
+
// 取消上车寻路
|
|
2077
|
+
cancelBoarding() {
|
|
2078
|
+
this.isMovingToBoardingPoint = false;
|
|
2079
|
+
this.boardingWaypoints = [];
|
|
2080
|
+
this.currentWaypointIndex = 0;
|
|
2081
|
+
this.boardingTargetDir = null;
|
|
2082
|
+
}
|
|
2083
|
+
// 注册所有事件
|
|
2466
2084
|
onAllEvent() {
|
|
2467
2085
|
this.isupdate = true;
|
|
2468
2086
|
this.setPointerLock();
|
|
2469
|
-
window.addEventListener("keydown", this.
|
|
2470
|
-
window.addEventListener("keyup", this.
|
|
2471
|
-
window.addEventListener("mousemove", this.
|
|
2472
|
-
window.addEventListener("click", this.
|
|
2087
|
+
window.addEventListener("keydown", this.boundOnKeydown);
|
|
2088
|
+
window.addEventListener("keyup", this.boundOnKeyup);
|
|
2089
|
+
window.addEventListener("mousemove", this.mouseMove);
|
|
2090
|
+
window.addEventListener("click", this.mouseClick);
|
|
2473
2091
|
}
|
|
2092
|
+
// 注销所有事件
|
|
2474
2093
|
offAllEvent() {
|
|
2475
2094
|
this.isupdate = false;
|
|
2476
2095
|
document.exitPointerLock();
|
|
2477
|
-
window.removeEventListener("keydown", this.
|
|
2478
|
-
window.removeEventListener("keyup", this.
|
|
2479
|
-
window.removeEventListener("mousemove", this.
|
|
2480
|
-
window.removeEventListener("click", this.
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
this.
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
this.
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
bottom: "16px",
|
|
2495
|
-
width: `${JOY_SIZE + 40}px`,
|
|
2496
|
-
height: `${JOY_SIZE + 40}px`,
|
|
2497
|
-
touchAction: "none",
|
|
2498
|
-
zIndex: "999",
|
|
2499
|
-
pointerEvents: "auto",
|
|
2500
|
-
WebkitUserSelect: "none",
|
|
2501
|
-
userSelect: "none"
|
|
2502
|
-
});
|
|
2503
|
-
container.appendChild(this.joystickZoneEl);
|
|
2504
|
-
["touchstart", "touchmove", "touchend", "touchcancel"].forEach(
|
|
2505
|
-
(evtName) => {
|
|
2506
|
-
this.joystickZoneEl?.addEventListener(
|
|
2507
|
-
evtName,
|
|
2508
|
-
(e) => e.preventDefault(),
|
|
2509
|
-
{
|
|
2510
|
-
passive: false
|
|
2511
|
-
}
|
|
2512
|
-
);
|
|
2513
|
-
}
|
|
2514
|
-
);
|
|
2515
|
-
this.joystickManager = nipple.create({
|
|
2516
|
-
zone: this.joystickZoneEl,
|
|
2517
|
-
mode: "static",
|
|
2518
|
-
position: {
|
|
2519
|
-
left: `${(JOY_SIZE + 40) / 2}px`,
|
|
2520
|
-
bottom: `${(JOY_SIZE + 40) / 2}px`
|
|
2521
|
-
},
|
|
2522
|
-
color: "#ffffff",
|
|
2523
|
-
size: JOY_SIZE,
|
|
2524
|
-
multitouch: true,
|
|
2525
|
-
maxNumberOfNipples: 1
|
|
2526
|
-
});
|
|
2527
|
-
this.joystickManager.on("move", (_evt, data) => {
|
|
2528
|
-
if (!data) return;
|
|
2529
|
-
const rawX = data.vector?.x ?? 0;
|
|
2530
|
-
const rawY = data.vector?.y ?? 0;
|
|
2531
|
-
const distance = data.distance ?? 0;
|
|
2532
|
-
const deadzone = 0.5;
|
|
2533
|
-
const dirX = rawX > deadzone ? 1 : rawX < -deadzone ? -1 : 0;
|
|
2534
|
-
const dirY = rawY > deadzone ? 1 : rawY < -deadzone ? -1 : 0;
|
|
2535
|
-
const sprintThreshold = JOY_SIZE / 2;
|
|
2536
|
-
const isSprinting = distance >= sprintThreshold;
|
|
2537
|
-
const prev = this.prevJoyState || {
|
|
2538
|
-
dirX: 0,
|
|
2539
|
-
dirY: 0,
|
|
2540
|
-
shift: false
|
|
2541
|
-
};
|
|
2542
|
-
if (dirX === prev.dirX && dirY === prev.dirY && isSprinting === prev.shift)
|
|
2543
|
-
return;
|
|
2544
|
-
this.prevJoyState = { dirX, dirY, shift: isSprinting };
|
|
2545
|
-
this.setInput({ moveX: dirX, moveY: dirY, shift: isSprinting });
|
|
2546
|
-
});
|
|
2547
|
-
this.joystickManager.on("end", () => {
|
|
2548
|
-
const prev = this.prevJoyState || {
|
|
2549
|
-
dirX: 0,
|
|
2550
|
-
dirY: 0,
|
|
2551
|
-
shift: false
|
|
2552
|
-
};
|
|
2553
|
-
if (prev.dirX !== 0 || prev.dirY !== 0 || prev.shift !== false) {
|
|
2554
|
-
this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
|
|
2555
|
-
this.setInput({ moveX: 0, moveY: 0, shift: false });
|
|
2556
|
-
}
|
|
2557
|
-
});
|
|
2558
|
-
this.lookAreaEl = document.createElement("div");
|
|
2559
|
-
Object.assign(this.lookAreaEl.style, {
|
|
2560
|
-
position: "absolute",
|
|
2561
|
-
right: "0",
|
|
2562
|
-
bottom: "0",
|
|
2563
|
-
width: "50%",
|
|
2564
|
-
height: "100%",
|
|
2565
|
-
zIndex: "998",
|
|
2566
|
-
touchAction: "none",
|
|
2567
|
-
WebkitUserSelect: "none",
|
|
2568
|
-
userSelect: "none"
|
|
2569
|
-
});
|
|
2570
|
-
container.appendChild(this.lookAreaEl);
|
|
2571
|
-
["touchstart", "touchmove", "touchend", "touchcancel"].forEach(
|
|
2572
|
-
(evtName) => {
|
|
2573
|
-
this.lookAreaEl?.addEventListener(
|
|
2574
|
-
evtName,
|
|
2575
|
-
(e) => e.preventDefault(),
|
|
2576
|
-
{
|
|
2577
|
-
passive: false
|
|
2578
|
-
}
|
|
2579
|
-
);
|
|
2580
|
-
}
|
|
2581
|
-
);
|
|
2582
|
-
this.lookAreaEl.addEventListener("pointerdown", this.onPointerDown, {
|
|
2583
|
-
passive: false
|
|
2584
|
-
});
|
|
2585
|
-
this.lookAreaEl.addEventListener("pointermove", this.onPointerMove, {
|
|
2586
|
-
passive: false
|
|
2587
|
-
});
|
|
2588
|
-
this.lookAreaEl.addEventListener("pointerup", this.onPointerUp, {
|
|
2589
|
-
passive: false
|
|
2590
|
-
});
|
|
2591
|
-
this.lookAreaEl.addEventListener("pointercancel", this.onPointerUp, {
|
|
2592
|
-
passive: false
|
|
2593
|
-
});
|
|
2594
|
-
const createBtn = (rightPx, bottomPx, bgUrl) => {
|
|
2595
|
-
const btn = document.createElement("button");
|
|
2596
|
-
const styles = {
|
|
2597
|
-
position: "absolute",
|
|
2598
|
-
right: `${rightPx}px`,
|
|
2599
|
-
bottom: `${bottomPx}px`,
|
|
2600
|
-
width: "56px",
|
|
2601
|
-
height: "56px",
|
|
2602
|
-
zIndex: "1000",
|
|
2603
|
-
borderRadius: "50%",
|
|
2604
|
-
border: "2px solid black",
|
|
2605
|
-
background: "rgba(0,0,0)",
|
|
2606
|
-
padding: "20px",
|
|
2607
|
-
opacity: "0.95",
|
|
2608
|
-
touchAction: "none",
|
|
2609
|
-
fontSize: "14px",
|
|
2610
|
-
userSelect: "none",
|
|
2611
|
-
overflow: "hidden",
|
|
2612
|
-
boxSizing: "border-box",
|
|
2613
|
-
backgroundColor: "transparent",
|
|
2614
|
-
backgroundRepeat: "no-repeat, no-repeat",
|
|
2615
|
-
backgroundPosition: "center center, center center",
|
|
2616
|
-
backgroundSize: "100% 100%, 80% 80%"
|
|
2617
|
-
};
|
|
2618
|
-
if (bgUrl) {
|
|
2619
|
-
const overlayColor = "rgba(0,0,0,0.5)";
|
|
2620
|
-
styles.backgroundImage = `linear-gradient(${overlayColor}, ${overlayColor}), url("${bgUrl}")`;
|
|
2621
|
-
}
|
|
2622
|
-
Object.assign(btn.style, styles);
|
|
2623
|
-
container.appendChild(btn);
|
|
2624
|
-
["touchstart", "touchend", "touchcancel"].forEach((evtName) => {
|
|
2625
|
-
btn.addEventListener(evtName, (e) => e.preventDefault(), {
|
|
2626
|
-
passive: false
|
|
2627
|
-
});
|
|
2628
|
-
});
|
|
2629
|
-
return btn;
|
|
2630
|
-
};
|
|
2631
|
-
this.jumpBtnEl = createBtn(14, 14, jump_default);
|
|
2632
|
-
this.jumpBtnEl.addEventListener(
|
|
2633
|
-
"touchstart",
|
|
2634
|
-
(e) => {
|
|
2635
|
-
e.preventDefault();
|
|
2636
|
-
this.setInput({ jump: true });
|
|
2637
|
-
},
|
|
2638
|
-
{ passive: false }
|
|
2639
|
-
);
|
|
2640
|
-
this.jumpBtnEl.addEventListener(
|
|
2641
|
-
"touchend",
|
|
2642
|
-
(e) => {
|
|
2643
|
-
e.preventDefault();
|
|
2644
|
-
this.setInput({ jump: false });
|
|
2645
|
-
},
|
|
2646
|
-
{ passive: false }
|
|
2647
|
-
);
|
|
2648
|
-
this.jumpBtnEl.addEventListener(
|
|
2649
|
-
"touchcancel",
|
|
2650
|
-
(e) => {
|
|
2651
|
-
e.preventDefault();
|
|
2652
|
-
this.setInput({ jump: false });
|
|
2653
|
-
},
|
|
2654
|
-
{ passive: false }
|
|
2655
|
-
);
|
|
2656
|
-
this.flyBtnEl = createBtn(14, 14 + 80, fly_default);
|
|
2657
|
-
this.flyBtnEl.addEventListener(
|
|
2658
|
-
"touchstart",
|
|
2659
|
-
(e) => {
|
|
2660
|
-
e.preventDefault();
|
|
2661
|
-
this.setInput({ toggleFly: true });
|
|
2662
|
-
},
|
|
2663
|
-
{ passive: false }
|
|
2664
|
-
);
|
|
2665
|
-
this.viewBtnEl = createBtn(14, 14 + 200, view_default);
|
|
2666
|
-
this.viewBtnEl.addEventListener(
|
|
2667
|
-
"touchstart",
|
|
2668
|
-
(e) => {
|
|
2669
|
-
e.preventDefault();
|
|
2670
|
-
this.setInput({ toggleView: true });
|
|
2671
|
-
},
|
|
2672
|
-
{ passive: false }
|
|
2673
|
-
);
|
|
2674
|
-
this.vehicleBtnEl = createBtn(14 + 100, 14 + 120, vehicle_default);
|
|
2675
|
-
this.vehicleBtnEl.addEventListener(
|
|
2676
|
-
"touchstart",
|
|
2677
|
-
(e) => {
|
|
2678
|
-
e.preventDefault();
|
|
2679
|
-
this.setInput({ toggleVehicle: true });
|
|
2680
|
-
},
|
|
2681
|
-
{ passive: false }
|
|
2682
|
-
);
|
|
2683
|
-
}
|
|
2684
|
-
destroyMobileControls() {
|
|
2685
|
-
try {
|
|
2686
|
-
if (this.joystickManager && this.joystickManager.destroy) {
|
|
2687
|
-
this.joystickManager.destroy();
|
|
2688
|
-
this.joystickManager = null;
|
|
2689
|
-
}
|
|
2690
|
-
if (this.joystickZoneEl?.parentElement) {
|
|
2691
|
-
this.joystickZoneEl.parentElement.removeChild(
|
|
2692
|
-
this.joystickZoneEl
|
|
2693
|
-
);
|
|
2694
|
-
this.joystickZoneEl = null;
|
|
2695
|
-
}
|
|
2696
|
-
if (this.lookAreaEl?.parentElement) {
|
|
2697
|
-
this.lookAreaEl.parentElement.removeChild(this.lookAreaEl);
|
|
2698
|
-
this.lookAreaEl = null;
|
|
2699
|
-
}
|
|
2700
|
-
if (this.jumpBtnEl?.parentElement) {
|
|
2701
|
-
this.jumpBtnEl.parentElement.removeChild(this.jumpBtnEl);
|
|
2702
|
-
this.jumpBtnEl = null;
|
|
2703
|
-
}
|
|
2704
|
-
if (this.flyBtnEl?.parentElement) {
|
|
2705
|
-
this.flyBtnEl.parentElement.removeChild(this.flyBtnEl);
|
|
2706
|
-
this.flyBtnEl = null;
|
|
2707
|
-
}
|
|
2708
|
-
if (this.viewBtnEl?.parentElement) {
|
|
2709
|
-
this.viewBtnEl.parentElement.removeChild(this.viewBtnEl);
|
|
2710
|
-
this.viewBtnEl = null;
|
|
2711
|
-
}
|
|
2712
|
-
if (this.vehicleBtnEl?.parentElement) {
|
|
2713
|
-
this.vehicleBtnEl.parentElement.removeChild(this.vehicleBtnEl);
|
|
2714
|
-
this.vehicleBtnEl = null;
|
|
2715
|
-
}
|
|
2716
|
-
this.lookAreaEl?.removeEventListener(
|
|
2717
|
-
"pointerdown",
|
|
2718
|
-
this.onPointerDown
|
|
2719
|
-
);
|
|
2720
|
-
this.lookAreaEl?.removeEventListener(
|
|
2721
|
-
"pointermove",
|
|
2722
|
-
this.onPointerMove
|
|
2723
|
-
);
|
|
2724
|
-
this.lookAreaEl?.removeEventListener("pointerup", this.onPointerUp);
|
|
2725
|
-
this.lookAreaEl?.removeEventListener(
|
|
2726
|
-
"pointercancel",
|
|
2727
|
-
this.onPointerUp
|
|
2728
|
-
);
|
|
2729
|
-
} catch (e) {
|
|
2730
|
-
console.warn("\u9500\u6BC1\u79FB\u52A8\u7AEF\u6447\u6746\u63A7\u5236\u65F6\u51FA\u9519\uFF1A", e);
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
syncVehicleBtnEl(show) {
|
|
2734
|
-
if (!this.vehicleBtnEl) return;
|
|
2735
|
-
this.vehicleBtnEl.style.display = show ? "block" : "none";
|
|
2736
|
-
}
|
|
2737
|
-
syncControllerModeBtnEl() {
|
|
2738
|
-
if (!this.isShowMobileControls) return;
|
|
2739
|
-
if (this.controllerMode == 0) {
|
|
2740
|
-
this.flyBtnEl.style.display = "block";
|
|
2741
|
-
const overlayColor = "rgba(0,0,0,0.5)";
|
|
2742
|
-
this.jumpBtnEl.style.backgroundImage = `linear-gradient(${overlayColor}, ${overlayColor}), url("${jump_default}")`;
|
|
2743
|
-
} else {
|
|
2744
|
-
this.flyBtnEl.style.display = "none";
|
|
2745
|
-
const overlayColor = "rgba(0,0,0,0.5)";
|
|
2746
|
-
this.jumpBtnEl.style.backgroundImage = `linear-gradient(${overlayColor}, ${overlayColor}), url("${break_default}")`;
|
|
2747
|
-
this.jumpBtnEl.style.backgroundImage = `url(${break_default})`;
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
// ==================== 更新参数 ====================
|
|
2751
|
-
setMouseSensitivity(mouseSensity) {
|
|
2752
|
-
this.mouseSensity = mouseSensity;
|
|
2753
|
-
this.controls.rotateSpeed = this.mouseSensity * 0.05;
|
|
2754
|
-
}
|
|
2096
|
+
window.removeEventListener("keydown", this.boundOnKeydown);
|
|
2097
|
+
window.removeEventListener("keyup", this.boundOnKeyup);
|
|
2098
|
+
window.removeEventListener("mousemove", this.mouseMove);
|
|
2099
|
+
window.removeEventListener("click", this.mouseClick);
|
|
2100
|
+
}
|
|
2101
|
+
// ==================== 移动端同步 ====================
|
|
2102
|
+
// 同步移动端按钮
|
|
2103
|
+
syncMobileControllerMode() {
|
|
2104
|
+
this.mobileControls?.syncControllerModeBtn(this.controllerMode);
|
|
2105
|
+
}
|
|
2106
|
+
// ==================== Setters ====================
|
|
2107
|
+
// 设置鼠标灵敏度
|
|
2108
|
+
setMouseSensitivity(value) {
|
|
2109
|
+
this.mouseSensitivity = value;
|
|
2110
|
+
this.controls.rotateSpeed = value * 0.05;
|
|
2111
|
+
}
|
|
2112
|
+
// 设置重力
|
|
2755
2113
|
setGravity(gravity) {
|
|
2756
2114
|
this.gravity = gravity * this.playerModel.scale;
|
|
2757
2115
|
}
|
|
2116
|
+
// 设置跳跃高度
|
|
2758
2117
|
setJumpHeight(jumpHeight) {
|
|
2759
2118
|
this.jumpHeight = jumpHeight * this.playerModel.scale;
|
|
2760
2119
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2120
|
+
// 设置移动速度
|
|
2121
|
+
setPlayerSpeed(speed) {
|
|
2122
|
+
this.playerSpeed = speed * this.playerModel.scale;
|
|
2763
2123
|
this.curPlayerSpeed = this.playerSpeed;
|
|
2764
2124
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2125
|
+
// 设置飞行速度
|
|
2126
|
+
setPlayerFlySpeed(flySpeed) {
|
|
2127
|
+
this.playerFlySpeed = flySpeed * this.playerModel.scale;
|
|
2767
2128
|
}
|
|
2768
|
-
|
|
2769
|
-
|
|
2129
|
+
// 设置最小相机距离
|
|
2130
|
+
setMinCamDistance(dist) {
|
|
2131
|
+
this.minCamDistance = dist * this.playerModel.scale;
|
|
2770
2132
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2133
|
+
// 设置最大相机距离
|
|
2134
|
+
setMaxCamDistance(dist) {
|
|
2135
|
+
this.maxCamDistance = dist * this.playerModel.scale;
|
|
2773
2136
|
this.orginMaxCamDistance = this.maxCamDistance;
|
|
2774
2137
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2138
|
+
// 设置鼠标模式
|
|
2139
|
+
setThirdMouseMode(mode) {
|
|
2140
|
+
this.thirdMouseMode = mode;
|
|
2777
2141
|
this.setPointerLock();
|
|
2778
2142
|
}
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
this.
|
|
2143
|
+
// 设置滚轮缩放
|
|
2144
|
+
setEnableZoom(enable) {
|
|
2145
|
+
this.enableZoom = enable;
|
|
2146
|
+
this.controls.enableZoom = enable;
|
|
2782
2147
|
}
|
|
2148
|
+
// 设置调试显示
|
|
2783
2149
|
setDebug(debug) {
|
|
2784
2150
|
if (this.collider) this.scene.remove(this.collider);
|
|
2785
2151
|
if (debug) {
|
|
@@ -2790,6 +2156,7 @@ var PlayerController = class {
|
|
|
2790
2156
|
}
|
|
2791
2157
|
}
|
|
2792
2158
|
// ==================== 销毁 ====================
|
|
2159
|
+
// 销毁控制器
|
|
2793
2160
|
destroy() {
|
|
2794
2161
|
this.offAllEvent();
|
|
2795
2162
|
if (this.player) {
|
|
@@ -2810,7 +2177,8 @@ var PlayerController = class {
|
|
|
2810
2177
|
this.scene.remove(this.collider);
|
|
2811
2178
|
this.collider = null;
|
|
2812
2179
|
}
|
|
2813
|
-
this.
|
|
2180
|
+
this.mobileControls?.destroy();
|
|
2181
|
+
this.mobileControls = null;
|
|
2814
2182
|
for (const v of this.vehicles) {
|
|
2815
2183
|
this.scene.remove(v.vehicleGroup);
|
|
2816
2184
|
v.pathPlanner?.dispose();
|
|
@@ -2824,31 +2192,34 @@ function playerController() {
|
|
|
2824
2192
|
if (!controllerInstance) controllerInstance = new PlayerController();
|
|
2825
2193
|
const c = controllerInstance;
|
|
2826
2194
|
return {
|
|
2827
|
-
init: (opts,
|
|
2828
|
-
loadVehicleModel: (
|
|
2829
|
-
changeView: () => c.changeView(),
|
|
2830
|
-
reset: (pos) => c.reset(pos),
|
|
2195
|
+
init: (opts, cb) => c.init(opts, cb),
|
|
2196
|
+
loadVehicleModel: (opts) => c.loadVehicleModel(opts),
|
|
2831
2197
|
update: (dt) => c.update(dt),
|
|
2832
2198
|
destroy: () => c.destroy(),
|
|
2199
|
+
reset: (pos) => c.reset(pos),
|
|
2833
2200
|
setInput: (i) => c.setInput(i),
|
|
2201
|
+
changeView: () => c.changeView(),
|
|
2834
2202
|
getPosition: () => c.getPosition(),
|
|
2835
2203
|
getCenterScreenRaycastHit: () => c.getCenterScreenRaycastHit(),
|
|
2204
|
+
getCurrentPersonAnimationName: () => c.getCurrentPersonAnimationName(),
|
|
2836
2205
|
getPerson: () => c.person,
|
|
2837
2206
|
getActiveVehicle: () => c.activeVehicle,
|
|
2838
2207
|
getAllVehicles: () => c.vehicles,
|
|
2839
2208
|
switchPlayerModel: (model) => c.switchPlayerModel(model),
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2209
|
+
setPlayerScale: (scale) => c.setPlayerScale(scale),
|
|
2210
|
+
setMouseSensitivity: (v) => c.setMouseSensitivity(v),
|
|
2211
|
+
setGravity: (v) => c.setGravity(v),
|
|
2212
|
+
setJumpHeight: (v) => c.setJumpHeight(v),
|
|
2213
|
+
setPlayerSpeed: (v) => c.setPlayerSpeed(v),
|
|
2214
|
+
setPlayerFlySpeed: (v) => c.setPlayerFlySpeed(v),
|
|
2215
|
+
setMinCamDistance: (v) => c.setMinCamDistance(v),
|
|
2216
|
+
setMaxCamDistance: (v) => c.setMaxCamDistance(v),
|
|
2217
|
+
setThirdMouseMode: (v) => c.setThirdMouseMode(v),
|
|
2218
|
+
setEnableZoom: (v) => c.setEnableZoom(v),
|
|
2219
|
+
setDebug: (v) => c.setDebug(v),
|
|
2220
|
+
setOverShoulderView: (v) => c.setOverShoulderView(v),
|
|
2221
|
+
registerAnimation: (key, clipName, opts) => c.registerAnimation(key, clipName, opts),
|
|
2222
|
+
playAnimation: (key, opts) => c.playAnimation(key, opts)
|
|
2852
2223
|
};
|
|
2853
2224
|
}
|
|
2854
2225
|
function onAllEvent() {
|
|
@@ -2856,8 +2227,7 @@ function onAllEvent() {
|
|
|
2856
2227
|
controllerInstance.onAllEvent();
|
|
2857
2228
|
}
|
|
2858
2229
|
function offAllEvent() {
|
|
2859
|
-
|
|
2860
|
-
controllerInstance.offAllEvent();
|
|
2230
|
+
controllerInstance?.offAllEvent();
|
|
2861
2231
|
}
|
|
2862
2232
|
export {
|
|
2863
2233
|
offAllEvent,
|