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