three-base 2.0.168 → 2.0.172
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/FPScontrol.d.ts +41 -7
- package/FPScontrol.js +232 -155
- package/FPScontrol.ts +129 -55
- package/Ground1/Ground1.js +6 -6
- package/Ground1/Ground1.ts +6 -6
- package/MktMesh/MktMesh.d.ts +5 -1
- package/MktMesh/MktMesh.js +20 -7
- package/MktMesh/MktMesh.ts +20 -7
- package/MktMesh/panoSlotBind.js +18 -2
- package/MktMesh/panoSlotBind.ts +18 -1
- package/MktMesh/panoTileStitcher.d.ts +35 -7
- package/MktMesh/panoTileStitcher.js +71 -11
- package/MktMesh/panoTileStitcher.ts +73 -12
- package/MktMesh/types.d.ts +5 -1
- package/MktMesh/types.ts +5 -1
- package/ThreeBase.d.ts +2 -11
- package/ThreeBase.js +14 -26
- package/ThreeBase.ts +14 -26
- package/WaterFlow/WaterFlow.d.ts +1 -1
- package/WaterFlow/WaterFlow.js +1 -1
- package/WaterFlow/WaterFlow.ts +1 -1
- package/package.json +1 -1
- package/pass/bloom_pass.js +7 -14
- package/pass/bloom_pass.ts +7 -16
- package/pass/cover_pass.js +7 -15
- package/pass/cover_pass.ts +7 -17
- package/pass/debugDepthState.d.ts +2 -2
- package/pass/debugDepthState.ts +2 -2
- package/pass/glow_pass.js +8 -16
- package/pass/glow_pass.ts +8 -18
- package/pass/ssao_pass.d.ts +13 -44
- package/pass/ssao_pass.js +60 -78
- package/pass/ssao_pass.ts +74 -88
- package/pass/stroke_pass.js +10 -16
- package/pass/stroke_pass.ts +10 -18
- package/shaders/baseDepthVisShader.d.ts +1 -1
- package/shaders/baseDepthVisShader.js +1 -1
- package/shaders/baseDepthVisShader.ts +1 -1
- package/shaders/debugDepthCompareShader.d.ts +1 -1
- package/shaders/debugDepthCompareShader.js +1 -1
- package/shaders/debugDepthCompareShader.ts +1 -1
- package/shaders/debugExcludedShader.d.ts +6 -4
- package/shaders/debugExcludedShader.js +6 -4
- package/shaders/debugExcludedShader.ts +6 -4
package/FPScontrol.d.ts
CHANGED
|
@@ -183,7 +183,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
|
|
|
183
183
|
* 单步最大位移(连续碰撞检测用):每帧位移会被切成若干 ≤ 本值的小步,逐步做碰撞检测。
|
|
184
184
|
* ⚠️ 必须明显小于水平包络的可达距离(R=1.06 时沿法线 0.749~1.06m),否则掉帧时一步就跨过墙体。
|
|
185
185
|
* ⚠️ 本值同时决定「单次碰撞需要修正多深」——子步越小,玩家每次陷进墙里越浅,
|
|
186
|
-
*
|
|
186
|
+
* 迭代要走的格数就越少(一格 ≤ RETREAT_SKIN,总位移 ≈ 该子步的穿透深度),
|
|
187
187
|
* 不会出现「一下子回弹很多」。
|
|
188
188
|
* 0.25 → 0.1:单次最大穿透从 0.25 降到 0.1,回弹量同比变小(子步数相应上调)。
|
|
189
189
|
*/
|
|
@@ -291,6 +291,12 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
|
|
|
291
291
|
* ⇒ 取 **0.6**:保证停位垂距 > 玩家胶囊半径(0.35),不再穿模。
|
|
292
292
|
* ⭐ 2026-10-08 皇上旨:下限放宽到 **0.2**、上限收到 **1.0**(编辑框范围 0.2~1.0)。
|
|
293
293
|
* ⚠️ R<0.6 仍会贴墙穿模(R=0.5 贴到墙面、R=0.3 明显穿模),皇上按需拧;下限放开是给窄通道/贴墙演示留余地。
|
|
294
|
+
* ⭐ 2026-10-11 A 档复核(tmp→test/fpsCollideFit.test.ts,正对 1m 墙、胶囊半径 0.35):
|
|
295
|
+
* 停位现在**逐位等于 R**(眼停在面前 R 处 ⇒ 身体入墙深度 = 0.35 − R,一条直线):
|
|
296
|
+
* R=0.20 → 入墙 **0.1500m** R=0.30 → **0.0500m** R=0.35 → **0.0000m**
|
|
297
|
+
* R=0.50 / 0.60 / 1.00 → **0.0000m**(身体不出面)
|
|
298
|
+
* ⇒ 抖不抖与 R 无关(A 档后全档抖动 0.0000m);R<0.35 只剩「身体陷进墙多少」这一件事,
|
|
299
|
+
* 皇上 10-11 定「旋钮不动,只把数字写明」⇒ 要真焊死得改这里的下限,别在推离公式里偷偷加夹取。
|
|
294
300
|
*/
|
|
295
301
|
static readonly MIN_HORIZONTAL_RAY_RADIUS: number;
|
|
296
302
|
/** 水平射线半径 R 的安全上限(⭐ 2026-10-08 皇上旨收到 1.0:再大包络远大于停位需求、回弹精度已够,白烧射程) */
|
|
@@ -338,11 +344,6 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
|
|
|
338
344
|
*/
|
|
339
345
|
private _horizHit;
|
|
340
346
|
private _inputDirVec;
|
|
341
|
-
/**
|
|
342
|
-
* 本帧**进碰撞之前**的水平速率(米/秒)。皇上八旨要求「移动向量的长度保持不变」,
|
|
343
|
-
* 而碰撞会把 `playerVelocity.x/z` 清零,所以必须在 updatePlayer 入口先记一份。
|
|
344
|
-
*/
|
|
345
|
-
private _preCollideHorizSpeed;
|
|
346
347
|
private _previewRetreat;
|
|
347
348
|
/** 开启预览那一刻的胶囊两端(关闭时整体还原回去) */
|
|
348
349
|
private _obsSavedStart;
|
|
@@ -397,7 +398,18 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
|
|
|
397
398
|
private _probeRaycaster;
|
|
398
399
|
private _tempVec3;
|
|
399
400
|
private _candPos;
|
|
400
|
-
|
|
401
|
+
/**
|
|
402
|
+
* ⭐ 2026-10-11 A 档(解析贴合)专用临时量,全部复用避免 GC:
|
|
403
|
+
* · `_fitNormal` = 助手里「本轮这条命中面」指向玩家一侧的单位水平法线;
|
|
404
|
+
* · `_horizNormal` = 本轮**欠距最大**那面的法线(推离方向 + 速度闸门共用一个方向,绝不各算一遍);
|
|
405
|
+
* · `_fitVec` = (候选中心 − 命中点) 临时量;
|
|
406
|
+
* · `_rhombusAxisX/_rhombusAxisZ` = 本帧菱形两条对角轴的世界方向(随 yaw 转,恒水平)。
|
|
407
|
+
*/
|
|
408
|
+
private _fitNormal;
|
|
409
|
+
private _horizNormal;
|
|
410
|
+
private _fitVec;
|
|
411
|
+
private _rhombusAxisX;
|
|
412
|
+
private _rhombusAxisZ;
|
|
401
413
|
private _tempVec4;
|
|
402
414
|
private _tempVec5;
|
|
403
415
|
/**
|
|
@@ -447,6 +459,28 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
|
|
|
447
459
|
* @returns null = 拿不到法线(无 face 或零向量)→ 调用方按「维持旧行为」处理
|
|
448
460
|
*/
|
|
449
461
|
private _faceNormalWorldY;
|
|
462
|
+
/**
|
|
463
|
+
* ⭐⭐ 2026-10-11 皇上改判 A 档「解析贴合」:一条水平命中射线**沿本面法线还差多少才脱开**。
|
|
464
|
+
* 同时把该面的单位水平法线(指向玩家所在的一侧)写进 `this._fitNormal` 供调用方取用。
|
|
465
|
+
*
|
|
466
|
+
* 为什么要它:旧写法每轮对每条命中射线**整格**推 `RETREAT_SKIN`(1cm),而退出判据是「8 条射线
|
|
467
|
+
* 全部脱开」⇒ 落点必然**过冲最多一格**(实测弹回 1.05cm),下一帧再从过冲处走回来 =
|
|
468
|
+
* 皇上所见「顶进去又弹回来一直抖」(无头复现:抖峰峰 0.0193m,60fps 下 34 次往复/40 帧,
|
|
469
|
+
* 与 R / 墙厚 / 入射角 / 帧率全无关)。按欠距推到「刚好脱开」就不再有过冲。
|
|
470
|
+
*
|
|
471
|
+
* 判据(恒等式,不是拍的):碰撞包络是顶点在 ±R·x̂、±R·ẑ 的**菱形**,它沿方向 n 的支持距离
|
|
472
|
+
* req = R·max(|n·x̂|, |n·ẑ|) ∈ [R/√2, R]。候选中心沿 n 到命中面的垂距为 perp(在实体内为负),
|
|
473
|
+
* 那么「还差 deficit = req − perp」推满就正好贴在包络边上。
|
|
474
|
+
*
|
|
475
|
+
* 法线取向:**与射线行进方向相反**的一侧(three 对 FrontSide 只回正面命中,所以常态天然满足;
|
|
476
|
+
* DoubleSide 拿到背面时按此翻号)。⚠️ 不能按「指向中心」翻号——中心已经陷进 1m 厚墙板时,
|
|
477
|
+
* 那样翻会把人往墙体更深处推(旧写法正是靠两侧票互相抵消才卡在板正中)。
|
|
478
|
+
*
|
|
479
|
+
* @returns deficit(米,≤0 = 已经脱开)。退化场景返回 0:
|
|
480
|
+
* · 拿不到面法线(无 face / 零向量)
|
|
481
|
+
* · 命中面近乎水平(法线压平后是零向量,如贴着地面/台面掠射)⇒ 不该把它当墙侧推
|
|
482
|
+
*/
|
|
483
|
+
private _horizFitDeficit;
|
|
450
484
|
/**
|
|
451
485
|
* 脚下深探:往下问 VOID_PROBE_DOWN 这么长一段,有没有地面(2026-09-25,穿地板自救用)。
|
|
452
486
|
* @returns true = 下方有地面 → 只是正常腾空/下落,不是穿模
|
package/FPScontrol.js
CHANGED
|
@@ -254,17 +254,19 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
254
254
|
// far = R√2。菱形几何一个数没变(R=1.06 → 外接框 2.12×2.12、斜弦 45°、首尾闭合),
|
|
255
255
|
// 只是不再经过「局部单位」这道换算。竖向的 2cm 探测余量见 `VERTICAL_SKIN`。
|
|
256
256
|
//
|
|
257
|
-
// 【停位口径(现行唯一,皇上 2026-
|
|
258
|
-
//
|
|
259
|
-
// 减去当前垂距 = deficit
|
|
260
|
-
//
|
|
257
|
+
// 【停位口径(现行唯一,皇上 2026-10-11 A 档改判 = 「解析贴合 + 只削法向速度」)】
|
|
258
|
+
// 射线在 far 内命中 = 碰撞;「还差多远才脱开」= 菱形沿墙法线的可达距离
|
|
259
|
+
// reach = R·max(|n·右|, |n·前|) ∈ [R/√2, R] 减去当前垂距 = deficit,取全轮**最大**者当读数;
|
|
260
|
+
// 位置修正沿该面法线一格一格推,每格 = min(RETREAT_SKIN, deficit) ⇒ 大欠距仍走整格,
|
|
261
|
+
// **收尾那一格按欠距截短** ⇒ 落点正好贴在包络边上(旧写法最后一格也推满 1cm → 过冲 → 来回甩)。
|
|
262
|
+
// 速度只削「往墙里」那一份(v −(v·n)n),切向保留 ⇒ 贴墙能滑,撞墙不再「加速→清零」锯齿。
|
|
261
263
|
// ⇒ 停位仍只由 **R** 一个数决定(R=1.06 → 沿法线 0.749~1.06m);胶囊半径 0.35 ⇒ 身体与墙之间
|
|
262
264
|
// 还留 0.40~0.71m 空气隙,这是皇上 2026-09-06 终裁布局自带、且亲眼验收过的量,不要当 bug 修。
|
|
263
265
|
/**
|
|
264
266
|
* 单步最大位移(连续碰撞检测用):每帧位移会被切成若干 ≤ 本值的小步,逐步做碰撞检测。
|
|
265
267
|
* ⚠️ 必须明显小于水平包络的可达距离(R=1.06 时沿法线 0.749~1.06m),否则掉帧时一步就跨过墙体。
|
|
266
268
|
* ⚠️ 本值同时决定「单次碰撞需要修正多深」——子步越小,玩家每次陷进墙里越浅,
|
|
267
|
-
*
|
|
269
|
+
* 迭代要走的格数就越少(一格 ≤ RETREAT_SKIN,总位移 ≈ 该子步的穿透深度),
|
|
268
270
|
* 不会出现「一下子回弹很多」。
|
|
269
271
|
* 0.25 → 0.1:单次最大穿透从 0.25 降到 0.1,回弹量同比变小(子步数相应上调)。
|
|
270
272
|
*/
|
|
@@ -405,6 +407,12 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
405
407
|
* ⇒ 取 **0.6**:保证停位垂距 > 玩家胶囊半径(0.35),不再穿模。
|
|
406
408
|
* ⭐ 2026-10-08 皇上旨:下限放宽到 **0.2**、上限收到 **1.0**(编辑框范围 0.2~1.0)。
|
|
407
409
|
* ⚠️ R<0.6 仍会贴墙穿模(R=0.5 贴到墙面、R=0.3 明显穿模),皇上按需拧;下限放开是给窄通道/贴墙演示留余地。
|
|
410
|
+
* ⭐ 2026-10-11 A 档复核(tmp→test/fpsCollideFit.test.ts,正对 1m 墙、胶囊半径 0.35):
|
|
411
|
+
* 停位现在**逐位等于 R**(眼停在面前 R 处 ⇒ 身体入墙深度 = 0.35 − R,一条直线):
|
|
412
|
+
* R=0.20 → 入墙 **0.1500m** R=0.30 → **0.0500m** R=0.35 → **0.0000m**
|
|
413
|
+
* R=0.50 / 0.60 / 1.00 → **0.0000m**(身体不出面)
|
|
414
|
+
* ⇒ 抖不抖与 R 无关(A 档后全档抖动 0.0000m);R<0.35 只剩「身体陷进墙多少」这一件事,
|
|
415
|
+
* 皇上 10-11 定「旋钮不动,只把数字写明」⇒ 要真焊死得改这里的下限,别在推离公式里偷偷加夹取。
|
|
408
416
|
*/
|
|
409
417
|
static MIN_HORIZONTAL_RAY_RADIUS = 0.2;
|
|
410
418
|
/** 水平射线半径 R 的安全上限(⭐ 2026-10-08 皇上旨收到 1.0:再大包络远大于停位需求、回弹精度已够,白烧射程) */
|
|
@@ -464,7 +472,8 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
464
472
|
digits: 2,
|
|
465
473
|
unit: "m",
|
|
466
474
|
frozen: true,
|
|
467
|
-
desc: "菱形半对角线 R,外接框边长 = 2R(= 离墙多远开始判定碰撞的半径)。越大包络越大、回弹方向越准;R<0.6 仍会贴墙穿模(R=0.5 贴墙面、R=0.3 明显穿模),已放宽到 0.2~1.0
|
|
475
|
+
desc: "菱形半对角线 R,外接框边长 = 2R(= 离墙多远开始判定碰撞的半径)。越大包络越大、回弹方向越准;R<0.6 仍会贴墙穿模(R=0.5 贴墙面、R=0.3 明显穿模),已放宽到 0.2~1.0 供按需拧。" +
|
|
476
|
+
"⭐ 2026-10-11 A 档实测:停位逐位等于 R ⇒ 身体入墙深度 = 0.35 − R(R=0.2 → 0.15m、R=0.3 → 0.05m、R≥0.35 → 0m);抖动与 R 无关(全档 0.0000m),旋钮范围按皇上旨不动",
|
|
468
477
|
get: (fps) => fps.CHORD_RHOMBUS_R,
|
|
469
478
|
set: (fps, v) => fps.setHorizontalRayRadius(v),
|
|
470
479
|
},
|
|
@@ -581,7 +590,7 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
581
590
|
step: 0.005,
|
|
582
591
|
digits: 3,
|
|
583
592
|
unit: "m",
|
|
584
|
-
desc: "⭐ 2026-
|
|
593
|
+
desc: "⭐ 2026-10-11 皇上 A 档改判:每轮**只沿「欠距最大那一面」的法线**推一格,且这一格取 min(本值, 该面欠距) ⇒ 大欠距仍是一格一格往外走(皇上 10-07「1 个单位 1 个单位的修正」),只有**收尾那一格按欠距截短**,落点贴在包络边上(旧写法每条命中射线各推满一格、最后一格也推满 ⇒ 过冲 ⇒ 贴墙来回甩 = 抖动)。" +
|
|
585
594
|
"⚠️ 上限以下别贪小:0.03/0.02/0.01 体检台判级完全相同,拧到 0.005 才有改善但「跳跃+冲刺+掉帧」那个现场开始穿墙(格长 = 每帧出水量)。迭代推到全部射线脱开为止",
|
|
586
595
|
},
|
|
587
596
|
{
|
|
@@ -761,11 +770,6 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
761
770
|
// 缓存本帧的按键输入单位方向(水平)。initPlayerInputDir() 每物理步刷新一次;
|
|
762
771
|
// 零向量表示「本帧没按方向键」→ 最终方向退化为纯反弹方向。
|
|
763
772
|
_inputDirVec = new Vector3();
|
|
764
|
-
/**
|
|
765
|
-
* 本帧**进碰撞之前**的水平速率(米/秒)。皇上八旨要求「移动向量的长度保持不变」,
|
|
766
|
-
* 而碰撞会把 `playerVelocity.x/z` 清零,所以必须在 updatePlayer 入口先记一份。
|
|
767
|
-
*/
|
|
768
|
-
_preCollideHorizSpeed = 0;
|
|
769
773
|
// ========== 回弹预览(皇上 2026-09-25 旨:「加个开关可以关闭碰撞修正位置的计算,方便观察回弹向量」)==========
|
|
770
774
|
// 打开后:**不写位置**(见 playerCollisions 末尾唯一的 translate 闸门),但方向/推离量照常算、
|
|
771
775
|
// 黄→白方向线照常画 → 肉眼核对「算出来的方向对不对」而不被人推开。
|
|
@@ -877,10 +881,20 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
877
881
|
_probeRaycaster = new Raycaster();
|
|
878
882
|
// 临时向量(复用避免 GC)
|
|
879
883
|
_tempVec3 = new Vector3();
|
|
880
|
-
// xsh 移植(2026-09-28):候选位置(= 控制器位置 + positionIncremental
|
|
881
|
-
// 与推离向量专用临时量(复用避免 GC)
|
|
884
|
+
// xsh 移植(2026-09-28):候选位置(= 控制器位置 + positionIncremental,射线原点锚)临时量
|
|
882
885
|
_candPos = new Vector3();
|
|
883
|
-
|
|
886
|
+
/**
|
|
887
|
+
* ⭐ 2026-10-11 A 档(解析贴合)专用临时量,全部复用避免 GC:
|
|
888
|
+
* · `_fitNormal` = 助手里「本轮这条命中面」指向玩家一侧的单位水平法线;
|
|
889
|
+
* · `_horizNormal` = 本轮**欠距最大**那面的法线(推离方向 + 速度闸门共用一个方向,绝不各算一遍);
|
|
890
|
+
* · `_fitVec` = (候选中心 − 命中点) 临时量;
|
|
891
|
+
* · `_rhombusAxisX/_rhombusAxisZ` = 本帧菱形两条对角轴的世界方向(随 yaw 转,恒水平)。
|
|
892
|
+
*/
|
|
893
|
+
_fitNormal = new Vector3();
|
|
894
|
+
_horizNormal = new Vector3();
|
|
895
|
+
_fitVec = new Vector3();
|
|
896
|
+
_rhombusAxisX = new Vector3(1, 0, 0);
|
|
897
|
+
_rhombusAxisZ = new Vector3(0, 0, 1);
|
|
884
898
|
_tempVec4 = new Vector3();
|
|
885
899
|
// 位移细分用的临时向量(与 playerCollisions 内的 _tempVec4 分开,避免被覆盖)
|
|
886
900
|
_tempVec5 = new Vector3();
|
|
@@ -1066,6 +1080,43 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1066
1080
|
return null;
|
|
1067
1081
|
return n.normalize().y;
|
|
1068
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* ⭐⭐ 2026-10-11 皇上改判 A 档「解析贴合」:一条水平命中射线**沿本面法线还差多少才脱开**。
|
|
1085
|
+
* 同时把该面的单位水平法线(指向玩家所在的一侧)写进 `this._fitNormal` 供调用方取用。
|
|
1086
|
+
*
|
|
1087
|
+
* 为什么要它:旧写法每轮对每条命中射线**整格**推 `RETREAT_SKIN`(1cm),而退出判据是「8 条射线
|
|
1088
|
+
* 全部脱开」⇒ 落点必然**过冲最多一格**(实测弹回 1.05cm),下一帧再从过冲处走回来 =
|
|
1089
|
+
* 皇上所见「顶进去又弹回来一直抖」(无头复现:抖峰峰 0.0193m,60fps 下 34 次往复/40 帧,
|
|
1090
|
+
* 与 R / 墙厚 / 入射角 / 帧率全无关)。按欠距推到「刚好脱开」就不再有过冲。
|
|
1091
|
+
*
|
|
1092
|
+
* 判据(恒等式,不是拍的):碰撞包络是顶点在 ±R·x̂、±R·ẑ 的**菱形**,它沿方向 n 的支持距离
|
|
1093
|
+
* req = R·max(|n·x̂|, |n·ẑ|) ∈ [R/√2, R]。候选中心沿 n 到命中面的垂距为 perp(在实体内为负),
|
|
1094
|
+
* 那么「还差 deficit = req − perp」推满就正好贴在包络边上。
|
|
1095
|
+
*
|
|
1096
|
+
* 法线取向:**与射线行进方向相反**的一侧(three 对 FrontSide 只回正面命中,所以常态天然满足;
|
|
1097
|
+
* DoubleSide 拿到背面时按此翻号)。⚠️ 不能按「指向中心」翻号——中心已经陷进 1m 厚墙板时,
|
|
1098
|
+
* 那样翻会把人往墙体更深处推(旧写法正是靠两侧票互相抵消才卡在板正中)。
|
|
1099
|
+
*
|
|
1100
|
+
* @returns deficit(米,≤0 = 已经脱开)。退化场景返回 0:
|
|
1101
|
+
* · 拿不到面法线(无 face / 零向量)
|
|
1102
|
+
* · 命中面近乎水平(法线压平后是零向量,如贴着地面/台面掠射)⇒ 不该把它当墙侧推
|
|
1103
|
+
*/
|
|
1104
|
+
_horizFitDeficit(worldPos, rayDir, interset) {
|
|
1105
|
+
const n = this._fitNormal;
|
|
1106
|
+
if (!this._transformFaceNormalWorld(interset, n))
|
|
1107
|
+
return 0;
|
|
1108
|
+
n.y = 0; // 本布局水平射线在 y=−0.7/−0.75(皇上 09-26 五旨),推离必须纯水平(同旧「偏差①」)
|
|
1109
|
+
if (n.lengthSq() < 1e-8)
|
|
1110
|
+
return 0;
|
|
1111
|
+
n.normalize();
|
|
1112
|
+
// 指向「射线来的那一侧」= 逆着射线行进方向,即离开实体的方向
|
|
1113
|
+
if (n.dot(rayDir) > 0)
|
|
1114
|
+
n.negate();
|
|
1115
|
+
const perp = n.dot(this._fitVec.copy(worldPos).sub(interset.point));
|
|
1116
|
+
const ax = Math.abs(n.dot(this._rhombusAxisX));
|
|
1117
|
+
const az = Math.abs(n.dot(this._rhombusAxisZ));
|
|
1118
|
+
return this.CHORD_RHOMBUS_R * Math.max(ax, az) - perp;
|
|
1119
|
+
}
|
|
1069
1120
|
/**
|
|
1070
1121
|
* 脚下深探:往下问 VOID_PROBE_DOWN 这么长一段,有没有地面(2026-09-25,穿地板自救用)。
|
|
1071
1122
|
* @returns true = 下方有地面 → 只是正常腾空/下落,不是穿模
|
|
@@ -1348,11 +1399,10 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1348
1399
|
controls = (deltaTime) => {
|
|
1349
1400
|
const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8) * this.speed;
|
|
1350
1401
|
// ⭐ 2026-09-26 皇上旨「碰撞就是按射线拾取算的,不用搞太复杂」:
|
|
1351
|
-
// 旧的「接触法线缓存 + 加速度保速投影」整块删除 ——
|
|
1352
|
-
//
|
|
1353
|
-
//
|
|
1354
|
-
//
|
|
1355
|
-
// 见 playerCollisions 末尾那段。清零本身照旧。
|
|
1402
|
+
// 旧的「接触法线缓存 + 加速度保速投影」整块删除 —— 这里**只管往输入方向加速**,
|
|
1403
|
+
// 撞墙的手感全部由 playerCollisions 负责:位置侧「解析贴合」(欠距最大那面推格、
|
|
1404
|
+
// 最后一格截短),速度侧「只削往墙里那一份」(⚠️ 2026-10-11 A 档改判,此处不再是
|
|
1405
|
+
// playerVelocity.x/z 整条清零 ⇒ 贴墙能沿墙滑行,见 playerCollisions 末尾那段)。
|
|
1356
1406
|
const accel = this._accelVec.set(0, 0, 0);
|
|
1357
1407
|
if (this.keyStates["KeyW"]) {
|
|
1358
1408
|
accel.addScaledVector(this.getForwardVector(), speedDelta);
|
|
@@ -1393,9 +1443,6 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1393
1443
|
damping *= 0.1;
|
|
1394
1444
|
}
|
|
1395
1445
|
this.playerVelocity.addScaledVector(this.playerVelocity, damping);
|
|
1396
|
-
// ⭐ 2026-09-26 皇上八旨「位置修正结束后…移动向量的长度保持不变」:本帧**进碰撞前**的
|
|
1397
|
-
// 水平速率快照。碰撞会把速度清零,子步跑完再想要「原来的长度」就没有了,必须在入口取。
|
|
1398
|
-
this._preCollideHorizSpeed = Math.hypot(this.playerVelocity.x, this.playerVelocity.z);
|
|
1399
1446
|
// 位移细分(连续碰撞):把本帧位移切成若干 ≤ MAX_STEP_DISTANCE 的小步,逐步推进并检测。
|
|
1400
1447
|
// 不细分的话,掉帧(deltaTime 被 clamp 到 0.05)时单步位移可达 25×0.025 ≈ 0.6,
|
|
1401
1448
|
// 远大于射线的检测余量(沿法线最小 reach R/√2 = 0.749 − 子步 0.1)→ 直接跨过墙体(穿墙)。
|
|
@@ -1460,11 +1507,15 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1460
1507
|
// ⇒ 本方法整体 = xsh `FPSControls.tsx → computerPosition()` 的忠实移植,骨架只有:
|
|
1461
1508
|
// ① while 循环(皇上 09-03 定「一帧合计不超过 100」,10-03 旨改到 500;骨架与 xsh 的 i<100 同形);
|
|
1462
1509
|
// ② 每轮在「候选位置」(真实位置 + positionIncremental)上打 8 条水平斜弦射线,
|
|
1463
|
-
//
|
|
1464
|
-
//
|
|
1465
|
-
//
|
|
1510
|
+
// ⭐ 2026-10-11 皇上 A 档改判:每条命中算「沿本面法线还差多少才脱开」(deficit),
|
|
1511
|
+
// **只取欠距最大的那一面**,沿它的法线推 min(RETREAT_SKIN, deficit) ——
|
|
1512
|
+
// 一格一格的外形保留(皇上 10-07 旨「应该 1 个单位 1 个单位的修正」⇒ RETREAT_SKIN=0.01),
|
|
1513
|
+
// 但收尾那一格按欠距截短 ⇒ 落点贴包络边,不再整格过冲(旧 xsh 各票各推一整格 ⇒ 必过冲);
|
|
1514
|
+
// 累加进 positionIncremental,并 goOnComputer=true 续命下一轮;
|
|
1466
1515
|
// ③ 下射线命中 ⇒ 竖直速度清零 + y 钉到「命中面之上 DOWN_RANGE」(xsh: cv − distance);
|
|
1467
|
-
// 上射线命中 ⇒ 竖直速度清零 +
|
|
1516
|
+
// 上射线命中 ⇒ 竖直速度清零 + 修正(含三道闸天花板的血案守卫)
|
|
1517
|
+
// —— ⭐ 10-11 修好少括号的 bug 后,上射线**每轮都测**(旧代码被关进「下射线命中」分支里
|
|
1518
|
+
// ⇒ 腾空时跳起来顶不到天花板);
|
|
1468
1519
|
// ④ 某一轮全部射线脱开 ⇒ 收工;循环外唯一一次 translate 落地(血案护栏不变)。
|
|
1469
1520
|
// ⭐⭐ 2026-10-02 皇上旨「模拟修正,不是真实修正,把最终结果算好了再应用到真实的第一人称
|
|
1470
1521
|
// 控制器上面」⇒ 那个 translate 落的是**「本步位移 + 允许的修正」一次到位**:
|
|
@@ -1477,7 +1528,12 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1477
1528
|
// 本布局射线在 y=−0.7/−0.75(皇上 09-26 五旨),不压平会把 y 混进推离;
|
|
1478
1529
|
// · 水平射线 8 条(皇上 09-06/09-14 亲加的 6-9 共线反向组),xsh 原版 4 条;
|
|
1479
1530
|
// · 竖向保留 faceUp / 天花板三道闸守卫(「掉出世界」血案修复)。
|
|
1531
|
+
// · ⭐ 2026-10-11 A 档:**只沿「欠距最大那一面」的法线**推,且最后一格按欠距截短
|
|
1532
|
+
// (xsh 是每条命中各推一整格 ⇒ 4 条命中同一面墙一轮推 4cm,退出时必过冲)。
|
|
1480
1533
|
// 原 deficit / 票面合成(finalDir) / 逐格推离 / 白推保险丝 / 胶囊兜底机器整条拆除。
|
|
1534
|
+
// ⚠️ 措辞校正(10-11):上面这句里「deficit」那半已随 A 档**回归**(`_horizFitDeficit()`:
|
|
1535
|
+
// 沿命中面法线的菱形可达距离 R·max(|n·x̂|,|n·ẑ|) 减当前垂距);
|
|
1536
|
+
// 仍拆除的是「票面合成 finalDir / 白推保险丝 / 胶囊兜底」三件 —— 别照旧文案把它们捡回来。
|
|
1481
1537
|
const positionIncremental = this._tempVec4.set(0, 0, 0);
|
|
1482
1538
|
this._refreshInputDir(); // 台账 ix/iz 列还要用(与碰撞计算无关)
|
|
1483
1539
|
const recordDebug = (this.dev || this.thirdPerson) && isFinalStep;
|
|
@@ -1508,12 +1564,21 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1508
1564
|
for (const ray of this._rays) {
|
|
1509
1565
|
ray.updateWorld(yawQuat, worldPos);
|
|
1510
1566
|
}
|
|
1567
|
+
// ⭐ 2026-10-11 A 档:菱形两条对角轴的世界方向(只随 yaw,恒水平)——贴合量 req 要用它
|
|
1568
|
+
this._rhombusAxisX.set(1, 0, 0).applyQuaternion(yawQuat);
|
|
1569
|
+
this._rhombusAxisZ.set(0, 0, 1).applyQuaternion(yawQuat);
|
|
1511
1570
|
const horizontalFar = this.HORIZONTAL_FAR; // = R√2(xsh: far = direction.length())
|
|
1512
1571
|
const downFar = downRange + this.VERTICAL_SKIN;
|
|
1513
1572
|
const upFar = upRange + this.VERTICAL_SKIN;
|
|
1514
1573
|
const devVisualFarD = downRange;
|
|
1515
1574
|
const devVisualFarU = upRange;
|
|
1516
|
-
// ==========
|
|
1575
|
+
// ========== 水平半区(⭐ 2026-10-11 皇上改判 A 档 = 解析贴合,替代「每条命中各推一整格」)==========
|
|
1576
|
+
// 每轮:所有命中射线各算「沿本面法线还差多少才脱开」(deficit),**只取欠距最大的那一面**,
|
|
1577
|
+
// 沿它的法线推 `min(RETREAT_SKIN, deficit)` —— 一格 1cm 的上限照旧(皇上 10-07「1 个单位
|
|
1578
|
+
// 1 个单位的修正」),但**最后一格只推欠距本身**,落点贴在包络边上,不再整格过冲。
|
|
1579
|
+
// · 为什么不再各票相加:4 条命中同一面墙时旧写法一轮推 4cm,退出时必然过头;
|
|
1580
|
+
// · 为什么只取最大:墙角两面墙各欠 3cm 时,先脱开深的那面,下一轮再补另一面(≤2~3 轮)。
|
|
1581
|
+
let bestDeficit = 0;
|
|
1517
1582
|
for (let ri = 2; ri < this._rays.length; ri++) {
|
|
1518
1583
|
const ray = this._rays[ri];
|
|
1519
1584
|
ray.far = horizontalFar;
|
|
@@ -1543,25 +1608,24 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1543
1608
|
interset = intersets[j];
|
|
1544
1609
|
}
|
|
1545
1610
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1611
|
+
const deficit = this._horizFitDeficit(worldPos, ray.direction, interset);
|
|
1612
|
+
if (deficit > bestDeficit) {
|
|
1613
|
+
bestDeficit = deficit;
|
|
1614
|
+
this._horizNormal.copy(this._fitNormal); // 与推离方向同一个数,绝不分两处算
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (bestDeficit > 1e-6) {
|
|
1618
|
+
if (i > roundCap) {
|
|
1619
|
+
// ⭐ **轮次上限**用完 ⇒ 这一轮起水平一格都不再推。
|
|
1620
|
+
// 常态帧 = 皇上 10-07 点头新加的 ROUNDS_PER_SUBSTEP(默认 500 ⇒ 与旧行为同形)。
|
|
1621
|
+
// ⚠️ 只拦水平:竖向(踩地/顶头)仍照常,否则人会往地心沉(皇上 09-25 冻结口径)。
|
|
1622
|
+
}
|
|
1623
|
+
else {
|
|
1624
|
+
// ⭐ 皇上 10-07 旨「每执行一次,偏移量是多少」⇒ 一格一行、带累计的形态保留:
|
|
1625
|
+
// 大欠距仍是一格一格往外走,只有收尾那一格按欠距截短。
|
|
1626
|
+
positionIncremental.addScaledVector(this._horizNormal, Math.min(this.RETREAT_SKIN, bestDeficit));
|
|
1627
|
+
goOnComputer = true;
|
|
1628
|
+
this._horizHit = true;
|
|
1565
1629
|
}
|
|
1566
1630
|
}
|
|
1567
1631
|
// ========== 向下射线(索引 0 = upTodwomRay)==========
|
|
@@ -1624,117 +1688,123 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1624
1688
|
// **不能**沿用旧探针的 `!debugRecorded`:`debugRecorded = !recordDebug`,而 `recordDebug`
|
|
1625
1689
|
// 跟着面板「碰撞射线」(dev) 走 → dev 关着时恒 true → 整段被跳过,
|
|
1626
1690
|
// 这正是皇上「在控制台里根本看不到下射线数值」的原因。
|
|
1627
|
-
//
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
//
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1691
|
+
// ⭐ 2026-10-11 皇上旨「少括号的 bug 一起修」:这个 `}` 是 `if (downIntersets.length > 0)` 的
|
|
1692
|
+
// 闭合括号,历史上被吃掉了 ⇒ 下面整段「向上射线」被关进「下射线命中」分支里。
|
|
1693
|
+
// 症状:腾空时脚下射线打不到地面 → 上射线**一次都不测** → 跳起来顶不到天花板(穿板顶)。
|
|
1694
|
+
// 顺带的第二个坑:while 的闭合括号跟着后移到「应用推离」注释之后(原 1843 行那个 `}`),
|
|
1695
|
+
// 于是「更新调试射线」那段也在每轮迭代里跑一遍 —— 删掉那个多余括号后它回到循环外,
|
|
1696
|
+
// 正好对上它自己的标签「dev 模式的**最后一步**」,且每帧只做一次。
|
|
1697
|
+
}
|
|
1698
|
+
// ========== 向上射线(索引 1 = dwomToUpRay)==========
|
|
1699
|
+
const upRay = this._rays[1];
|
|
1700
|
+
upRay.far = upFar;
|
|
1701
|
+
upRay.applyToRaycaster(this._raycaster);
|
|
1702
|
+
let upIntersets = this._raycaster.intersectObjects(this._colliders, false);
|
|
1703
|
+
// 调试:记录向上射线
|
|
1704
|
+
if (!debugRecorded) {
|
|
1705
|
+
const hasHit = upIntersets.length > 0;
|
|
1706
|
+
let intersetPoint = null;
|
|
1707
|
+
if (hasHit) {
|
|
1708
|
+
let closestDist = Infinity;
|
|
1709
|
+
for (const int of upIntersets) {
|
|
1710
|
+
const d = int.point.distanceTo(worldPos);
|
|
1711
|
+
if (d < closestDist) {
|
|
1712
|
+
closestDist = d;
|
|
1713
|
+
intersetPoint = int.point;
|
|
1644
1714
|
}
|
|
1645
1715
|
}
|
|
1646
|
-
upRay.recordHit(intersetPoint, devVisualFarU);
|
|
1647
|
-
upRay.responded = hasHit;
|
|
1648
|
-
debugRecorded = true;
|
|
1649
1716
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
const upPen = upRange - interset.distance;
|
|
1661
|
-
if (upPen > 0 && downIntersets.length === 0
|
|
1662
|
-
&& this.playerVelocity.y <= 0 && !this._floorBelow(worldPos)) {
|
|
1663
|
-
// ⭐⭐ 2026-09-25 皇上旨「顶出来,不穿模」(「下射线会陷入地面很长一个距离」的正解)
|
|
1664
|
-
// 上射线打到东西、**脚下却没有任何地面**、且人不正在往上蹿 → 我已经在这块面的
|
|
1665
|
-
// 下方/内部,再往下顶就是把它当「天花板」往下钻 = 死亡路径:
|
|
1666
|
-
// 实测脚底 −1.5 起手(1m 厚地板,tmp/TMP_vert_trace.mjs)——
|
|
1667
|
-
// 前 10 帧人在板内、FrontSide 看不到背面 → 上下射线全瞎 → 重力累加上坠;
|
|
1668
|
-
// 第 11 帧眼落到板底之下 0.0135,上射线打到**板底(法线朝下)**,旧口径
|
|
1669
|
-
// `positionIncremental.y -= upPen` 一帧把人往下砸 **0.9865m** → 眼落到 −2.0,
|
|
1670
|
-
// 从此两条竖射线彻底够不着 → 自由落体到 **−13.161547** → 撞上 camera.y≤−10 的
|
|
1671
|
-
// 掉出世界复位(`this.playerCollider.end.set(0, 1, 10)`)→ 「陷进地面很长一段
|
|
1672
|
-
// 距离然后凭空回到原点」。这就是皇上看到的现象。
|
|
1673
|
-
// 新口径:**绝不往下顶**,一步顶到「脚底刚好落在这块面之上」(眼高 = 面 y +
|
|
1674
|
-
// downRange + SKIN)。顶完眼只比板底高一个 SKIN → 下一轮迭代下射线立刻
|
|
1675
|
-
// 看得到板顶(distance≈SKIN)→ downPen≈downRange 把人整段托上板面站稳,
|
|
1676
|
-
// 上下射线互为 backup,全程 ≤ 十几帧、不再掉出世界。
|
|
1677
|
-
// ⚠️ 三道闸缺一不可:
|
|
1678
|
-
// · `downIntersets.length===0`:站在下层地板上、头顶真天花板时,往下鸭/躲矮洞
|
|
1679
|
-
// 是正确行为,不能把人吸到上层去;
|
|
1680
|
-
// · `playerVelocity.y <= 0`:正往上跳、头顶撞到真天花板必须仍按普通天花板往下顶,
|
|
1681
|
-
// 否则「跳一下 → 被传上天花板」;
|
|
1682
|
-
// · `!_floorBelow()`:竖向射线只够 downFar(1.52)/upFar(0.32),而跳跃顶点 ≈1.06m 加上
|
|
1683
|
-
// 脚底悬浮 0.5m 已经超出下射线够得着的范围 →
|
|
1684
|
-
// 腾空时脚下本来就是瞎的,只靠上面两道闸会把「高空跳到天花板下方」也误判成穿模。往下深探
|
|
1685
|
-
// VOID_PROBE_DOWN(8m) 问得到地面 = 正常腾空/下落 → 维持旧的天花板下压。
|
|
1686
|
-
// ⚠️ 已知残留(实测 tmp/TMP_ceiling_ab.mjs ⑦):板**厚度 > DOWN_RANGE(1.5m)** 时,
|
|
1687
|
-
// 顶到「板底之上」后人仍在板内 → 再次全瞎下坠 → 在板底往复。
|
|
1688
|
-
// HEAD 版同一场景是「掉到 −10.9 → 凭空复位」,现在收敛为有界往复(不再掉出世界),
|
|
1689
|
-
// 彻底根治要加**背面探针**(FrontSide 看不到内部,量不到板顶)。
|
|
1690
|
-
this.playerVelocity.y = 0;
|
|
1691
|
-
const liftTo = interset.point.y + downRange + this.VERTICAL_SKIN - worldPos.y;
|
|
1692
|
-
if (liftTo > 1e-4) {
|
|
1693
|
-
positionIncremental.y += liftTo;
|
|
1694
|
-
this.playerOnFloor = true;
|
|
1695
|
-
goOnComputer = true;
|
|
1717
|
+
upRay.recordHit(intersetPoint, devVisualFarU);
|
|
1718
|
+
upRay.responded = hasHit;
|
|
1719
|
+
debugRecorded = true;
|
|
1720
|
+
}
|
|
1721
|
+
if (upIntersets.length > 0) {
|
|
1722
|
+
let interset = upIntersets[0];
|
|
1723
|
+
if (upIntersets.length > 1) {
|
|
1724
|
+
for (let j = 1; j < upIntersets.length; j++) {
|
|
1725
|
+
if (upIntersets[j].point.distanceTo(worldPos) < interset.point.distanceTo(worldPos)) {
|
|
1726
|
+
interset = upIntersets[j];
|
|
1696
1727
|
}
|
|
1697
1728
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1729
|
+
}
|
|
1730
|
+
// 按距离解算(与下射线一致):头顶离天花板 interset.distance,需保持 upRange → 下推 (upRange - distance)
|
|
1731
|
+
const upPen = upRange - interset.distance;
|
|
1732
|
+
if (upPen > 0 && downIntersets.length === 0
|
|
1733
|
+
&& this.playerVelocity.y <= 0 && !this._floorBelow(worldPos)) {
|
|
1734
|
+
// ⭐⭐ 2026-09-25 皇上旨「顶出来,不穿模」(「下射线会陷入地面很长一个距离」的正解)
|
|
1735
|
+
// 上射线打到东西、**脚下却没有任何地面**、且人不正在往上蹿 → 我已经在这块面的
|
|
1736
|
+
// 下方/内部,再往下顶就是把它当「天花板」往下钻 = 死亡路径:
|
|
1737
|
+
// 实测脚底 −1.5 起手(1m 厚地板,tmp/TMP_vert_trace.mjs)——
|
|
1738
|
+
// 前 10 帧人在板内、FrontSide 看不到背面 → 上下射线全瞎 → 重力累加上坠;
|
|
1739
|
+
// 第 11 帧眼落到板底之下 0.0135,上射线打到**板底(法线朝下)**,旧口径
|
|
1740
|
+
// `positionIncremental.y -= upPen` 一帧把人往下砸 **0.9865m** → 眼落到 −2.0,
|
|
1741
|
+
// 从此两条竖射线彻底够不着 → 自由落体到 **−13.161547** → 撞上 camera.y≤−10 的
|
|
1742
|
+
// 掉出世界复位(`this.playerCollider.end.set(0, 1, 10)`)→ 「陷进地面很长一段
|
|
1743
|
+
// 距离然后凭空回到原点」。这就是皇上看到的现象。
|
|
1744
|
+
// 新口径:**绝不往下顶**,一步顶到「脚底刚好落在这块面之上」(眼高 = 面 y +
|
|
1745
|
+
// downRange + SKIN)。顶完眼只比板底高一个 SKIN → 下一轮迭代下射线立刻
|
|
1746
|
+
// 看得到板顶(distance≈SKIN)→ downPen≈downRange 把人整段托上板面站稳,
|
|
1747
|
+
// 上下射线互为 backup,全程 ≤ 十几帧、不再掉出世界。
|
|
1748
|
+
// ⚠️ 三道闸缺一不可:
|
|
1749
|
+
// · `downIntersets.length===0`:站在下层地板上、头顶真天花板时,往下鸭/躲矮洞
|
|
1750
|
+
// 是正确行为,不能把人吸到上层去;
|
|
1751
|
+
// · `playerVelocity.y <= 0`:正往上跳、头顶撞到真天花板必须仍按普通天花板往下顶,
|
|
1752
|
+
// 否则「跳一下 → 被传上天花板」;
|
|
1753
|
+
// · `!_floorBelow()`:竖向射线只够 downFar(1.52)/upFar(0.32),而跳跃顶点 ≈1.06m 加上
|
|
1754
|
+
// 脚底悬浮 0.5m 已经超出下射线够得着的范围 →
|
|
1755
|
+
// 腾空时脚下本来就是瞎的,只靠上面两道闸会把「高空跳到天花板下方」也误判成穿模。往下深探
|
|
1756
|
+
// VOID_PROBE_DOWN(8m) 问得到地面 = 正常腾空/下落 → 维持旧的天花板下压。
|
|
1757
|
+
// ⚠️ 已知残留(实测 tmp/TMP_ceiling_ab.mjs ⑦):板**厚度 > DOWN_RANGE(1.5m)** 时,
|
|
1758
|
+
// 顶到「板底之上」后人仍在板内 → 再次全瞎下坠 → 在板底往复。
|
|
1759
|
+
// HEAD 版同一场景是「掉到 −10.9 → 凭空复位」,现在收敛为有界往复(不再掉出世界),
|
|
1760
|
+
// 彻底根治要加**背面探针**(FrontSide 看不到内部,量不到板顶)。
|
|
1761
|
+
this.playerVelocity.y = 0;
|
|
1762
|
+
const liftTo = interset.point.y + downRange + this.VERTICAL_SKIN - worldPos.y;
|
|
1763
|
+
if (liftTo > 1e-4) {
|
|
1764
|
+
positionIncremental.y += liftTo;
|
|
1765
|
+
this.playerOnFloor = true;
|
|
1701
1766
|
goOnComputer = true;
|
|
1702
1767
|
}
|
|
1703
1768
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
// 像素宽依赖画布尺寸:resize / 改 debugLineWidth 都在这里跟上
|
|
1709
|
-
this._syncDebugLineResolution();
|
|
1710
|
-
// ⭐ 每条 FpsRay 自带 debugLine,直接 syncDebugLine(起点/终点由 ray.recordHit 冻结,
|
|
1711
|
-
// 三态配色由 ray.syncDebugLine 写入)。射线与辅助线一一对应,不可能再错位。
|
|
1712
|
-
for (let di = 0; di < this._rays.length; di++) {
|
|
1713
|
-
// ⚠️ 皇上 2026-09-03:横贯中央的 ±X 侧向射线默认不画(debugHideSideRays 开关)。
|
|
1714
|
-
// (原 6/7 侧向已删;当前 6-9 是反向水平斜弦,isSideRay=false → 不受本开关影响,可见。
|
|
1715
|
-
// 皇上要「看到区别」→ 反向射线必须画出来。)若日后加回 ±X 侧向直射线,给那条 isSideRay 置 true 即自动隐藏。
|
|
1716
|
-
if (this.debugHideSideRays && this._rays[di].isSideRay)
|
|
1717
|
-
continue;
|
|
1718
|
-
const ray = this._rays[di];
|
|
1719
|
-
if (ray.debugLine)
|
|
1720
|
-
ray.syncDebugLine();
|
|
1769
|
+
else if (upPen > 0) {
|
|
1770
|
+
this.playerVelocity.y = 0;
|
|
1771
|
+
positionIncremental.y -= upPen;
|
|
1772
|
+
goOnComputer = true;
|
|
1721
1773
|
}
|
|
1722
1774
|
}
|
|
1723
|
-
// 应用推离
|
|
1724
|
-
// ⚠️⚠️ 这一段**只能出现一次**(2026-09-21 血案:重构时误插入第二份,
|
|
1725
|
-
// 位置推离量直接翻倍 → 陷 0.42m 被推 0.84m、垂距冲到 0.94(该停在 0.52)
|
|
1726
|
-
// → 皇上看到的「从侧面撞墙角一下子弹很远」)。改动本文件时务必确认
|
|
1727
|
-
// `positionIncremental` 全文件只被 `translate` 应用一次。
|
|
1728
|
-
// ⭐⭐ 2026-09-25 皇上旨「加个开关可以关闭碰撞修正位置的计算,方便观察回弹向量」:
|
|
1729
|
-
// 唯一的**位置写入口**就是这一行 → 在这一个闸门上关,才是真的「不修正位置」。
|
|
1730
|
-
// 原先 `previewRetreat`(面板「回弹预览」)只跳过了上方 `!previewRetreat` 那个**水平**
|
|
1731
|
-
// 修正块,竖向(下射线 `y += downPen` / 上射线 `y ± upPen`)照样从这里落地 →
|
|
1732
|
-
// 标签写着「不修正位置」却仍在被顶,且陷进地面时每轮迭代累加一份 downPen,
|
|
1733
|
-
// 预览模式下人被竖直弹飞。
|
|
1734
|
-
// ⚠️ 只跳「写位置」,不跳「记账」:`playerVelocity.y = 0`、`playerOnFloor = true`
|
|
1735
|
-
// 与削速度/方向票全部保留 → 观察时不会一帧帧攒重力陷到地心,也不会触发
|
|
1736
|
-
// camera.y ≤ −10 的掉出世界复位。
|
|
1737
1775
|
}
|
|
1776
|
+
// ========== 更新调试射线(dev 模式的最后一步)==========
|
|
1777
|
+
if (recordDebug) {
|
|
1778
|
+
this._initDebugLines();
|
|
1779
|
+
// 像素宽依赖画布尺寸:resize / 改 debugLineWidth 都在这里跟上
|
|
1780
|
+
this._syncDebugLineResolution();
|
|
1781
|
+
// ⭐ 每条 FpsRay 自带 debugLine,直接 syncDebugLine(起点/终点由 ray.recordHit 冻结,
|
|
1782
|
+
// 三态配色由 ray.syncDebugLine 写入)。射线与辅助线一一对应,不可能再错位。
|
|
1783
|
+
for (let di = 0; di < this._rays.length; di++) {
|
|
1784
|
+
// ⚠️ 皇上 2026-09-03:横贯中央的 ±X 侧向射线默认不画(debugHideSideRays 开关)。
|
|
1785
|
+
// (原 6/7 侧向已删;当前 6-9 是反向水平斜弦,isSideRay=false → 不受本开关影响,可见。
|
|
1786
|
+
// 皇上要「看到区别」→ 反向射线必须画出来。)若日后加回 ±X 侧向直射线,给那条 isSideRay 置 true 即自动隐藏。
|
|
1787
|
+
if (this.debugHideSideRays && this._rays[di].isSideRay)
|
|
1788
|
+
continue;
|
|
1789
|
+
const ray = this._rays[di];
|
|
1790
|
+
if (ray.debugLine)
|
|
1791
|
+
ray.syncDebugLine();
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
// 应用推离
|
|
1795
|
+
// ⚠️⚠️ 这一段**只能出现一次**(2026-09-21 血案:重构时误插入第二份,
|
|
1796
|
+
// 位置推离量直接翻倍 → 陷 0.42m 被推 0.84m、垂距冲到 0.94(该停在 0.52)
|
|
1797
|
+
// → 皇上看到的「从侧面撞墙角一下子弹很远」)。改动本文件时务必确认
|
|
1798
|
+
// `positionIncremental` 全文件只被 `translate` 应用一次。
|
|
1799
|
+
// ⭐⭐ 2026-09-25 皇上旨「加个开关可以关闭碰撞修正位置的计算,方便观察回弹向量」:
|
|
1800
|
+
// 唯一的**位置写入口**就是这一行 → 在这一个闸门上关,才是真的「不修正位置」。
|
|
1801
|
+
// 原先 `previewRetreat`(面板「回弹预览」)只跳过了上方 `!previewRetreat` 那个**水平**
|
|
1802
|
+
// 修正块,竖向(下射线 `y += downPen` / 上射线 `y ± upPen`)照样从这里落地 →
|
|
1803
|
+
// 标签写着「不修正位置」却仍在被顶,且陷进地面时每轮迭代累加一份 downPen,
|
|
1804
|
+
// 预览模式下人被竖直弹飞。
|
|
1805
|
+
// ⚠️ 只跳「写位置」,不跳「记账」:`playerVelocity.y = 0`、`playerOnFloor = true`
|
|
1806
|
+
// 与削速度/方向票全部保留 → 观察时不会一帧帧攒重力陷到地心,也不会触发
|
|
1807
|
+
// camera.y ≤ −10 的掉出世界复位。
|
|
1738
1808
|
// ⭐⭐ 2026-10-02 皇上旨(原话):「在修正位置的时候,我希望的是**模拟修正**,而不是**真实的
|
|
1739
1809
|
// 修正**,在模拟修正中把最终结果计算好了,再应用到真实的第一人称控制器上面」。
|
|
1740
1810
|
// ⇒ 上面那个 while 全在**模拟位置**(真实位置 + 本步位移 `_subStep`)上跑;真实 collider
|
|
@@ -1767,15 +1837,22 @@ export default class FPScontrol extends EventDispatcher {
|
|
|
1767
1837
|
}
|
|
1768
1838
|
// 本步已消费:归零,面板那两颗直接调 playerCollisions 的按钮不会重复走一步
|
|
1769
1839
|
this._subStep.set(0, 0, 0);
|
|
1770
|
-
// ⭐⭐ 2026-10-
|
|
1771
|
-
//
|
|
1772
|
-
//
|
|
1773
|
-
//
|
|
1774
|
-
//
|
|
1775
|
-
//
|
|
1840
|
+
// ⭐⭐ 2026-10-11 皇上旨「撞墙会进入墙体然后反弹回来,一直抖动」→ A 档改判:
|
|
1841
|
+
// **只削掉「往墙里」那一份速度**(沿命中面法线的负分量),切向分量原样保留。
|
|
1842
|
+
// 旧口径(2026-10-07「撞到墙后,加速度置为 0」)是 x/z 双双归零,两处毛病:
|
|
1843
|
+
// ① 斜着撞墙 = 连「沿墙滑行」那份一起抹掉 → 手感像被墙吸停,且人是沿坐标轴方向被清,
|
|
1844
|
+
// 不是沿墙法线;速度被清零的方向与实际推离方向不共线 → 推离与加速度交替占上风;
|
|
1845
|
+
// ② 全清 ⇒ 每帧「输入重新加速 → 立刻清零」的锯齿,正是他看到的抖动的另一半来源
|
|
1846
|
+
// (实测峰峰 0.0193m、60fps 下约 17 次/秒翻向)。
|
|
1847
|
+
// 新口径:`into = v·n`(n = 本轮欠距最大那面的**外法线**,指向玩家一侧),只在 into<0
|
|
1848
|
+
// (= 正在往墙里冲)时减掉 `into·n` 那份 ⇒ 撞后 v·n = 0(非穿透),切向 v−(v·n)n 完整保留。
|
|
1849
|
+
// ⚠️ 方向与推离用的是**同一个** `_horizNormal`(水平半区那轮算好、就地复用),绝不另算一遍。
|
|
1850
|
+
// ⚠️ 竖向 y 一字不动(重力/跳跃照旧);观察模式(回弹预览)不改速度,口径与 10-07 一致。
|
|
1776
1851
|
if (this._horizHit && !this.previewRetreat) {
|
|
1777
|
-
this.playerVelocity.
|
|
1778
|
-
|
|
1852
|
+
const into = this.playerVelocity.dot(this._horizNormal);
|
|
1853
|
+
if (into < 0) {
|
|
1854
|
+
this.playerVelocity.addScaledVector(this._horizNormal, -into);
|
|
1855
|
+
}
|
|
1779
1856
|
}
|
|
1780
1857
|
// ⭐ 十旨:一帧一条的抖动台账。**只由子步循环的最后一步(isFinalStep)记** ——
|
|
1781
1858
|
// 中间小步是碰撞细分的过渡状态,记了就变成「一帧好几条」,翻向/跳变的口径全乱。
|