three-base 2.0.172 → 2.0.175

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/FPScontrol.d.ts +15 -24
  2. package/FPScontrol.js +63 -87
  3. package/FPScontrol.ts +64 -88
  4. package/Ground1/Ground1.d.ts +4 -2
  5. package/Ground1/Ground1.js +77 -57
  6. package/Ground1/Ground1.ts +77 -57
  7. package/Mirror.js +0 -4
  8. package/Mirror.ts +0 -4
  9. package/MktMesh/MktMesh.d.ts +3 -2
  10. package/MktMesh/MktMesh.js +27 -8
  11. package/MktMesh/MktMesh.ts +24 -8
  12. package/MktMesh/mktMappingShader.d.ts +6 -5
  13. package/MktMesh/mktMappingShader.js +26 -19
  14. package/MktMesh/mktMappingShader.ts +28 -19
  15. package/MktMesh/mktShader.d.ts +127 -58
  16. package/MktMesh/mktShader.js +192 -117
  17. package/MktMesh/mktShader.ts +251 -127
  18. package/MktMesh/panoSlotRoster.d.ts +2 -2
  19. package/MktMesh/panoSlotRoster.js +9 -7
  20. package/MktMesh/panoSlotRoster.ts +9 -7
  21. package/MktMesh/panoTileStitcher.d.ts +20 -13
  22. package/MktMesh/panoTileStitcher.js +97 -76
  23. package/MktMesh/panoTileStitcher.ts +119 -90
  24. package/PipeMesh/PipeJoint.d.ts +22 -0
  25. package/PipeMesh/PipeJoint.js +33 -0
  26. package/PipeMesh/PipeJoint.ts +38 -0
  27. package/PipeMesh/PipeMesh.d.ts +94 -22
  28. package/PipeMesh/PipeMesh.js +192 -53
  29. package/PipeMesh/PipeMesh.ts +223 -76
  30. package/PipeMesh/PipeStraight.d.ts +19 -0
  31. package/PipeMesh/PipeStraight.js +34 -0
  32. package/PipeMesh/PipeStraight.ts +45 -0
  33. package/PipeMesh/axisSnap.d.ts +52 -0
  34. package/PipeMesh/axisSnap.js +52 -0
  35. package/PipeMesh/axisSnap.ts +93 -0
  36. package/PipeMesh/directRoute.d.ts +85 -0
  37. package/PipeMesh/directRoute.js +126 -0
  38. package/PipeMesh/directRoute.ts +190 -0
  39. package/PipeMesh/index.d.ts +6 -0
  40. package/PipeMesh/index.js +6 -0
  41. package/PipeMesh/index.ts +7 -1
  42. package/PipeMesh/pipeMaterial.d.ts +16 -0
  43. package/PipeMesh/pipeMaterial.js +23 -0
  44. package/PipeMesh/pipeMaterial.ts +26 -0
  45. package/ThreeBase.d.ts +1 -39
  46. package/ThreeBase.js +38 -147
  47. package/ThreeBase.ts +1176 -1302
  48. package/instanced/InstancedBatch.d.ts +14 -1
  49. package/instanced/InstancedBatch.js +53 -3
  50. package/instanced/InstancedBatch.ts +62 -3
  51. package/instanced/InstancedChild.d.ts +24 -0
  52. package/instanced/InstancedChild.js +40 -0
  53. package/instanced/InstancedChild.ts +41 -0
  54. package/instanced/InstancedGroup.d.ts +48 -0
  55. package/instanced/InstancedGroup.js +45 -1
  56. package/instanced/InstancedGroup.ts +85 -0
  57. package/package.json +2 -7
  58. package/pass/debugDepthState.d.ts +16 -8
  59. package/pass/debugDepthState.js +4 -0
  60. package/pass/debugDepthState.ts +27 -9
  61. package/pass/glow_pass.js +11 -94
  62. package/pass/glow_pass.ts +11 -97
  63. package/pass/ssao_pass.d.ts +9 -23
  64. package/pass/ssao_pass.js +41 -128
  65. package/pass/ssao_pass.ts +36 -166
  66. package/pass/stroke_pass.js +3 -0
  67. package/pass/stroke_pass.ts +4 -0
  68. package/shaders/debugDepthCompareShader.d.ts +1 -1
  69. package/shaders/debugDepthCompareShader.js +1 -1
  70. package/shaders/debugDepthCompareShader.ts +1 -1
  71. package/shaders/debugDepthJudgeShader.d.ts +1 -1
  72. package/shaders/debugDepthJudgeShader.js +1 -1
  73. package/shaders/debugDepthJudgeShader.ts +1 -1
  74. package/PipeMesh/PathGeometry.d.ts +0 -44
  75. package/PipeMesh/PathGeometry.js +0 -307
  76. package/PipeMesh/PathGeometry.ts +0 -343
  77. package/PipeMesh/PathPoint.d.ts +0 -17
  78. package/PipeMesh/PathPoint.js +0 -31
  79. package/PipeMesh/PathPoint.ts +0 -36
  80. package/PipeMesh/PathPointList.d.ts +0 -31
  81. package/PipeMesh/PathPointList.js +0 -209
  82. package/PipeMesh/PathPointList.ts +0 -227
  83. package/PipeMesh/PathTubeGeometry.d.ts +0 -25
  84. package/PipeMesh/PathTubeGeometry.js +0 -130
  85. package/PipeMesh/PathTubeGeometry.ts +0 -146
  86. package/WaterFlow/FloatingDuck.d.ts +0 -88
  87. package/WaterFlow/FloatingDuck.js +0 -307
  88. package/WaterFlow/FloatingDuck.ts +0 -445
  89. package/WaterFlow/GPUFluidSimulation.d.ts +0 -56
  90. package/WaterFlow/GPUFluidSimulation.js +0 -785
  91. package/WaterFlow/GPUFluidSimulation.ts +0 -827
  92. package/WaterFlow/ReservoirSystem.d.ts +0 -106
  93. package/WaterFlow/ReservoirSystem.js +0 -621
  94. package/WaterFlow/ReservoirSystem.ts +0 -754
  95. package/WaterFlow/SPHField.d.ts +0 -35
  96. package/WaterFlow/SPHField.js +0 -204
  97. package/WaterFlow/SPHField.ts +0 -228
  98. package/WaterFlow/ScreenSpaceFluidPass.d.ts +0 -69
  99. package/WaterFlow/ScreenSpaceFluidPass.js +0 -736
  100. package/WaterFlow/ScreenSpaceFluidPass.ts +0 -789
  101. package/WaterFlow/ShallowWaterSurface.d.ts +0 -45
  102. package/WaterFlow/ShallowWaterSurface.js +0 -441
  103. package/WaterFlow/ShallowWaterSurface.ts +0 -510
  104. package/WaterFlow/WaterFlow.d.ts +0 -87
  105. package/WaterFlow/WaterFlow.js +0 -171
  106. package/WaterFlow/WaterFlow.ts +0 -219
  107. package/WaterFlow/types.d.ts +0 -161
  108. package/WaterFlow/types.js +0 -1
  109. package/WaterFlow/types.ts +0 -189
  110. package/WaterFlow/waterFlowPass.d.ts +0 -3
  111. package/WaterFlow/waterFlowPass.js +0 -20
  112. package/WaterFlow/waterFlowPass.ts +0 -40
  113. package/shaders/debugExcludedShader.d.ts +0 -15
  114. package/shaders/debugExcludedShader.js +0 -48
  115. package/shaders/debugExcludedShader.ts +0 -50
package/FPScontrol.ts CHANGED
@@ -6,12 +6,10 @@
6
6
  */
7
7
  import { BufferAttribute, Camera, Clock, Euler, EventDispatcher, Group, InstancedMesh, Intersection, Matrix3, Matrix4, Mesh, Object3D, Quaternion, Ray, Raycaster, Scene, Vector3 } from "three";
8
8
  import { MathUtils } from "three";
9
- // (2026-09-28 皇上旨「完完全全使用 xsh-react-three 的碰撞计算方法」:胶囊兜底整条拆除。
10
- // collisionOBB.ts 纯几何模块与其单测保留,但 FPScontrol 不再引用。)
9
+ // 碰撞完全由围绕玩家视点的射线完成;FPScontrol 不使用任何体积碰撞体。
11
10
  const { degToRad, clamp } = MathUtils;
12
11
 
13
12
  // 「矩形射线法」的水平 far 口径:斜弦画长 = R√2(四弦首尾闭合的唯一解,勿再改回)。
14
- import { Capsule } from "three/examples/jsm/math/Capsule";
15
13
  // 粗辅助线:WebGL 下 `LineBasicMaterial.linewidth` 恒被驱动钳成 1px(Chrome/ANGLE 一律 1px,
16
14
  // 调大无效),要真的变粗必须走 `LineSegments2 + LineMaterial`(shader 里按像素铺quad)。
17
15
  // 本仓库先例:src/views/home/homeRender.ts:71-73, 344-358。
@@ -263,7 +261,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
263
261
  /** 是否允许跳跃(空格触发)。false 时按空格不跳。默认 true */
264
262
  public jumpEnabled: boolean = true;
265
263
  /** 跳跃初速度(垂直方向,m/s)。
266
- * ⚠️ 2026-09-27 治「跳跃翻墙」:原 15(当时重力 30)→ 顶点 3.75m 远超墙高 2m,跳起后胶囊底越过墙顶
264
+ * ⚠️ 2026-09-27 治「跳跃翻墙」:原 15(当时重力 30)→ 顶点 3.75m 远超墙高 2m,跳起后视点越过墙顶
267
265
  * 造成水平失明窗口,人带着水平速度飞越墙平面、落地在墙外侧被越推越远(探针实测 z=-187)。
268
266
  * 定案 8 ⇒ 顶点 1.07m:仍可跳上 0.8m 高台,且 yBottom 恒低于「墙顶 2.0 − 步高 0.1」,
269
267
  * 与主循环竖直门配合后失明窗口彻底消失。**顶点**才是防翻墙的那条线,不是初速度本身。
@@ -311,7 +309,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
311
309
  // 位置修正沿该面法线一格一格推,每格 = min(RETREAT_SKIN, deficit) ⇒ 大欠距仍走整格,
312
310
  // **收尾那一格按欠距截短** ⇒ 落点正好贴在包络边上(旧写法最后一格也推满 1cm → 过冲 → 来回甩)。
313
311
  // 速度只削「往墙里」那一份(v −(v·n)n),切向保留 ⇒ 贴墙能滑,撞墙不再「加速→清零」锯齿。
314
- // ⇒ 停位仍只由 **R** 一个数决定(R=1.06 → 沿法线 0.749~1.06m);胶囊半径 0.35 ⇒ 身体与墙之间
312
+ // ⇒ 停位仍只由 **R** 一个数决定(R=1.06 → 沿法线 0.749~1.06m);射线包络决定视点与墙之间
315
313
  // 还留 0.40~0.71m 空气隙,这是皇上 2026-09-06 终裁布局自带、且亲眼验收过的量,不要当 bug 修。
316
314
  /**
317
315
  * 单步最大位移(连续碰撞检测用):每帧位移会被切成若干 ≤ 本值的小步,逐步做碰撞检测。
@@ -324,8 +322,8 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
324
322
  public MAX_STEP_DISTANCE: number = 0.1;
325
323
  /** 位移细分的最大段数(防止极端速度/长卡顿把一帧切成几百步拖垮帧率) */
326
324
  public MAX_STEP_COUNT: number = 32;
327
- // 垂直碰撞范围 = 从 playerCollider.end(眼睛)到「该射线所保护的那一面」的距离
328
- // = capsule length + radius = (1.0 - 0.35) + 0.35 = 1.0(几何真值;下方两个常量是**口径**,不是几何)
325
+ // 垂直碰撞范围 = 从玩家视点到「该射线所保护的那一面」的距离
326
+ // = 视点到脚下射线保护面的距离 1.0(下方两个常量是**口径**,不是几何)
329
327
  // 对应 xsh-react-three 中 collideVolume(=3)= 控制器到地面的距离
330
328
  // xsh 中 far = collideVolume, 响应 = collideVolume - distance
331
329
  // 此处 far = DOWN_RANGE/UP_RANGE + SKIN,响应 = DOWN_RANGE/UP_RANGE − distance
@@ -334,9 +332,9 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
334
332
  // 我提醒过这一条常量身兼两职(探测长度 + 站位基准),他追选定案「**直接改站位基准**」,
335
333
  // 所以拆成两条各自独立的常量,**不再**共用 VERTICAL_RANGE=1.0:
336
334
  // · DOWN_RANGE = 1.5:下射线 far = 1.5+SKIN,响应 downPen = 1.5 − distance
337
- // → 落地后**眼睛离地 1.5m**。胶囊脚底在眼下 1.0m ⇒ 站定时脚底离地 **0.5m(悬浮)**。
335
+ // → 落地后**眼睛离地 1.5m**。
338
336
  // · UP_RANGE = 0.3:上射线 far = 0.3+SKIN,响应 upPen = 0.3 − distance
339
- // → 头顶到面时**眼睛离顶 0.3m**。胶囊顶在眼下 0.35m ⇒ 真顶到矮面时头顶穿入面内 **0.05m**,
337
+ // → 头顶到面时**眼睛离顶 0.3m**,由上射线阻止继续上移。
340
338
  // 且 0.3~1.0m 之间的低矮障碍物从此探不到(上射线变短的直接后果)。
341
339
  // ⚠️ 跳跃/重力/台阶吸附的其余口径一字未动,只换这两个基准数。
342
340
  // ⭐ 2026-09-26 皇上旨「能调节的参数都做滚轮输入框」→ 两条改为公共可写(面板「参数旋钮」可拧),
@@ -451,14 +449,14 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
451
449
  * 即 R 就是「玩家中心离墙多远开始判定碰撞」的半径。若 R 小于停位需求,人必须**先陷进墙里**
452
450
  * 才会被射线检测到,随后每帧被位置修正弹回一点 → 皇上所见「侧撞弹很远」。
453
451
  * 探针实测(`tmp/TMP_R_side_effect.mjs`,斜 45° 撞墙角 / 正对单墙同口径):
454
- * R=0.30 → 停位垂距 **0.2128**(< 胶囊半径 0.35,**明显穿模**)每帧被推 0.040
452
+ * R=0.30 → 停位垂距 **0.2128**,射线包络过小,容易贴墙。
455
453
  * R=0.50 → 0.3541(贴到墙面) 每帧 0.036
456
- * R=0.60 → 0.4243(勉强 > 胶囊半径)0.034
454
+ * R=0.60 → 0.4243。
457
455
  * R=0.80 → 0.5448 ✓ / R=1.06 → 0.7247 ✓(每帧修正仅 0.002)
458
- * ⇒ 取 **0.6**:保证停位垂距 > 玩家胶囊半径(0.35),不再穿模。
456
+ * ⇒ 取 **0.6**:为射线包络保留足够的贴墙间距。
459
457
  * ⭐ 2026-10-08 皇上旨:下限放宽到 **0.2**、上限收到 **1.0**(编辑框范围 0.2~1.0)。
460
458
  * ⚠️ R<0.6 仍会贴墙穿模(R=0.5 贴到墙面、R=0.3 明显穿模),皇上按需拧;下限放开是给窄通道/贴墙演示留余地。
461
- * ⭐ 2026-10-11 A 档复核(tmp→test/fpsCollideFit.test.ts,正对 1m 墙、胶囊半径 0.35):
459
+ * ⭐ 2026-10-11 A 档复核(tmp→test/fpsCollideFit.test.ts,正对 1m 墙):
462
460
  * 停位现在**逐位等于 R**(眼停在面前 R 处 ⇒ 身体入墙深度 = 0.35 − R,一条直线):
463
461
  * R=0.20 → 入墙 **0.1500m** R=0.30 → **0.0500m** R=0.35 → **0.0000m**
464
462
  * R=0.50 / 0.60 / 1.00 → **0.0000m**(身体不出面)
@@ -647,19 +645,6 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
647
645
  desc: "⭐ 2026-10-11 皇上 A 档改判:每轮**只沿「欠距最大那一面」的法线**推一格,且这一格取 min(本值, 该面欠距) ⇒ 大欠距仍是一格一格往外走(皇上 10-07「1 个单位 1 个单位的修正」),只有**收尾那一格按欠距截短**,落点贴在包络边上(旧写法每条命中射线各推满一格、最后一格也推满 ⇒ 过冲 ⇒ 贴墙来回甩 = 抖动)。" +
648
646
  "⚠️ 上限以下别贪小:0.03/0.02/0.01 体检台判级完全相同,拧到 0.005 才有改善但「跳跃+冲刺+掉帧」那个现场开始穿墙(格长 = 每帧出水量)。迭代推到全部射线脱开为止",
649
647
  },
650
- {
651
- group: "回弹与手感",
652
- label: "每小帧轮数上限",
653
- key: "ROUNDS_PER_SUBSTEP",
654
- min: 1,
655
- max: 500,
656
- step: 1,
657
- digits: 0,
658
- unit: "轮",
659
- desc: "⭐ 皇上 2026-10-07 裁决加:一个物理小帧里 while 最多走几轮就落地(默认 500 = 与「每帧迭代预算」同档 ⇒ 现状一字不变)。" +
660
- "**这颗才是「一次修多少」的真闸门**:10-07 实测每次落地量 = 这一小帧往墙里挤进多深(2/8/10cm 都这么来),跟一格多远无关 ⇒ 要「一次只修几格、下一小帧再修」只能拧这里。" +
661
- "⚠️ 拧小的代价:一秒能挤出去的总量变小,高速冲刺顶墙时「挤进来的比推出去的快」⇒ 越陷越深(格长拧到 0.005 那档体检台 40 号现场就判 ❌穿墙,同一个机理)。先拧着看手感,别把默认改小",
662
- },
663
648
  // ===== 移动与视角 =====
664
649
  {
665
650
  group: "移动与视角",
@@ -829,7 +814,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
829
814
  // ⚠️ 只闸水平+竖向的**位置写入**;削速度、playerOnFloor、事件派发全部保留,
830
815
  // 否则预览模式下重力会一帧帧攒着把人送到地心并触发掉出世界复位。
831
816
  // ⭐⭐ 2026-09-26 皇上七旨「回弹预览开了, 就关不掉」⇒ 本字段改成**带暂存的访问器**:
832
- // 开 = 记下当前胶囊位置;关 = **还原**那个位置 + 清掉水平速度。
817
+ // 开 = 记下当前视点位置;关 = **还原**那个位置 + 清掉水平速度。
833
818
  // 病根(实测,墙面板块 x∈[5.0,6.0]、R=1.06):预览既然不写位置,人就照着自己的
834
819
  // 行走速度(speed=6 ⇒ 终端 37.5m/s ⇒ 每帧 0.62m)一路**穿墙走出去**,120 帧后中心
835
820
  // 已经在 x=138;这一路上只要停在任何一处「两面墙的票互相抵消」的几何里,位置修正就
@@ -837,15 +822,13 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
837
822
  // deficit 恒为 0.5600、位移 0。看着就是「预览关了位置修正也不回来了」。
838
823
  // ⇒ 预览是**观察**用的,那一段时间里的位移本来就不作数,关掉即回到打开前那一刻。
839
824
  private _previewRetreat: boolean = false;
840
- /** 开启预览那一刻的胶囊两端(关闭时整体还原回去) */
841
- private _obsSavedStart: Vector3 = new Vector3();
842
- private _obsSavedEnd: Vector3 = new Vector3();
825
+ /** 开启预览那一刻的玩家视点位置(关闭时还原) */
826
+ private _obsSavedPosition: Vector3 = new Vector3();
843
827
  /** 是否已有可还原的暂存(没进过观察模式就没有,避免构造期赋值把人还原到原点) */
844
828
  private _obsHasSave: boolean = false;
845
- /** 进观察模式(回弹预览 / 单步):记下当前胶囊位置,退出时还原 */
829
+ /** 进观察模式(回弹预览 / 单步):记下当前视点位置,退出时还原 */
846
830
  private _saveObsPos(): void {
847
- this._obsSavedStart.copy(this.playerCollider.start);
848
- this._obsSavedEnd.copy(this.playerCollider.end);
831
+ this._obsSavedPosition.copy(this.playerPosition);
849
832
  this._obsHasSave = true;
850
833
  }
851
834
  /**
@@ -855,9 +838,8 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
855
838
  */
856
839
  private _restoreObsPos(): void {
857
840
  if (!this._obsHasSave) return;
858
- this.playerCollider.start.copy(this._obsSavedStart);
859
- this.playerCollider.end.copy(this._obsSavedEnd);
860
- this.camera.position.copy(this.playerCollider.end);
841
+ this.playerPosition.copy(this._obsSavedPosition);
842
+ this.camera.position.copy(this.playerPosition);
861
843
  this.playerVelocity.x = 0;
862
844
  this.playerVelocity.z = 0;
863
845
  this._obsHasSave = false;
@@ -875,8 +857,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
875
857
  // xsh 的骨架只有一个 while(`FPSControls.tsx` 的 `computerPosition`):
876
858
  // ① 在**候选位置**(真实位置 + 已累计的 positionIncremental)上打水平射线
877
859
  // ② 有命中 ⇒ 沿「离开命中点」的方向推**一小格**(它写死 0.03)并续跑下一轮
878
- // ③ 一轮下来没命中 ⇒ goOnComputer=false ⇒ 收工;整圈上限 = MAX_ITERATIONS(皇上 10-03 旨:500)轮,
879
- // 单趟 while 另有「本轮数上限」闸门(常态帧 = ROUNDS_PER_SUBSTEP,皇上 10-07 裁决加,默认 500 同档 ⇒ 不变)
860
+ // ③ 一轮下来没命中 ⇒ goOnComputer=false ⇒ 收工;整圈上限 = MAX_ITERATIONS(皇上 10-03 旨:500)轮
880
861
  // ④ 循环外**唯一一次** position.add(incremental)
881
862
  // ⇒ 旧的「测试法逐级逼近」(六旨:1 → 0.1 → 0.01 三档、武装/判定/接受/撤销、每帧出水量 ≥
882
863
  // 进水量那套配套保险丝)**整条删除**。位置仍是一格一格挪出去的(皇上要的一步一步看那个形态不变),
@@ -902,18 +883,6 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
902
883
  private _tempEuler: Euler = new Euler(0, 0, 0, "YXZ");
903
884
  // 最大迭代次数(皇上 2026-10-03 旨:「那计算次数改成不超过 500」⇒ 100 → 500)
904
885
  public MAX_ITERATIONS: number = 500;
905
- /**
906
- * 一个**物理小帧**里 while 最多走几轮就落地(皇上 2026-10-07 裁决「加一个每小帧轮数上限」)。
907
- * 默认 500 = 与 `MAX_ITERATIONS` 同档 ⇒ 现状一字不变;拧小才是他要的「一次只修几格、下一小帧再修」。
908
- * ⭐ 为什么拧格长没用(10-07 实测):常态帧会一直迭代到「8 条射线全部脱开」才落地 ⇒
909
- * **每次落地量 = 这一小帧往墙里挤进多深**,与一格多远无关(格长 0.03→0.01 判级一字未变)。
910
- * 一格 = 一轮里每条命中各推的距离 ⇒ 要限制"一次修多少"只能限制**轮数**。
911
- * ⚠️ 代价(不许瞒):出水量 = 轮数 × 每轮票数 × 格长,默认每帧 2 个小帧。拧到 1 轮 + 1cm 格长 ⇒
912
- * 一秒最多挤出约 120cm 的量看着够,但高速冲刺顶墙时「这一帧挤进来的」可能比「两小帧推出去的」多
913
- * ⇒ 越陷越深,格长拧到 0.005 那一档体检台 40 号现场就是这么判 **❌穿墙** 的。
914
- * 所以默认不动,先让他在线上拧着看手感。
915
- */
916
- public ROUNDS_PER_SUBSTEP: number = 500;
917
886
  // ⚠️ 每帧碰撞迭代总预算(皇上 2026-09-03:墙角偶尔卡住,要求碰撞计算次数不超过 100;
918
887
  // 2026-10-03 同一位皇上把这条上限抬到 500 —— 机制一字未改,只是额度变大):
919
888
  // while 循环原本的上限 100 是「单次 playerCollisions」的,而一个 update() 帧内会调
@@ -941,6 +910,10 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
941
910
  */
942
911
  private _fitNormal: Vector3 = new Vector3();
943
912
  private _horizNormal: Vector3 = new Vector3();
913
+ /** 本子步实际推离过的不同墙面法线。墙角需要同时裁掉两个朝内速度分量,
914
+ * 不能只用最后一次命中的面,否则两个面会在相邻帧交替抢占速度闸门而抖动。 */
915
+ private _horizContactNormals: Vector3[] = [new Vector3(), new Vector3(), new Vector3(), new Vector3()];
916
+ private _horizContactCount: number = 0;
944
917
  private _fitVec: Vector3 = new Vector3();
945
918
  private _rhombusAxisX: Vector3 = new Vector3(1, 0, 0);
946
919
  private _rhombusAxisZ: Vector3 = new Vector3(0, 0, 1);
@@ -1234,14 +1207,12 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1234
1207
  resetPlayer(position: Vector3 = new Vector3(0, 0, 0)): void {
1235
1208
  this.playerVelocity.set(0, 0, 0);
1236
1209
  this.prePlayerVelocity.set(0, 0, 0);
1237
- this.playerCollider.start.set(position.x, position.y + 0.35, position.z);
1238
- this.playerCollider.end.set(position.x, position.y + 1, position.z);
1239
- this.playerCollider.radius = 0.35;
1240
- this.camera.position.copy(this.playerCollider.end);
1241
- // ⭐ 复位 = 换现场 ⇒ 把相机/胶囊位置基准挪到新位置。
1210
+ this.playerPosition.set(position.x, position.y + 1, position.z);
1211
+ this.camera.position.copy(this.playerPosition);
1212
+ // ⭐ 复位 = 换现场 ⇒ 把相机/视点位置基准挪到新位置。
1242
1213
  }
1243
- // 玩家胶囊碰撞体
1244
- playerCollider: Capsule = new Capsule(new Vector3(0, 0.35, 0), new Vector3(0, 1, 0), 0.35);
1214
+ /** 玩家视点。所有碰撞射线均以此点为局部原点。 */
1215
+ playerPosition: Vector3 = new Vector3(0, 1, 0);
1245
1216
  // 时钟对象
1246
1217
  clock: Clock = new Clock();
1247
1218
  prePlayerVelocity: Vector3 = new Vector3();
@@ -1288,7 +1259,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1288
1259
  * `depthWrite:true`(three-base/Ground1/Ground1.ts:107);哪天地板改回
1289
1260
  * transparent + depthWrite:false,射线线会重新穿地浮出来。
1290
1261
  * - 回弹方向线(黄→白)= **false**:「回弹向量线保持穿透显示」。
1291
- * 它是「本帧要看的向量」,起点在胶囊内部、方向常指着墙里,开遮挡会被自己的身子
1262
+ * 它是「本帧要看的向量」,起点在玩家位置、方向常指着墙里,开遮挡会被墙面
1292
1263
  * 或墙面吃掉半截 → 观察目的直接落空,故保持常显在最前。
1293
1264
  */
1294
1265
  private _createDebugLine(depthTest: boolean): LineSegments2 {
@@ -1356,7 +1327,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1356
1327
  this._debugRayGroup = null;
1357
1328
  }
1358
1329
  init(canvas: HTMLCanvasElement): void {
1359
- this.playerCollider.end.set(0, 1, 0);
1330
+ this.playerPosition.set(0, 1, 0);
1360
1331
  document.addEventListener("keydown", this.keydown);
1361
1332
  document.addEventListener("keyup", this.keyup);
1362
1333
  canvas.addEventListener("pointerdown", this.mousedown);
@@ -1509,7 +1480,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1509
1480
  this.playerCollisions(s === stepCount - 1);
1510
1481
  }
1511
1482
  // 设置相机位置(第一人称默认值)
1512
- this.camera.position.copy(this.playerCollider.end);
1483
+ this.camera.position.copy(this.playerPosition);
1513
1484
  // 第三人称:偏移相机位置 + 朝向第一人称坐标
1514
1485
  if (this.thirdPerson) {
1515
1486
  const cosPitch = Math.cos(this._thirdPersonAngleX);
@@ -1524,7 +1495,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1524
1495
  Math.cos(this._thirdPersonAngleY) *
1525
1496
  cosPitch *
1526
1497
  this.thirdPersonDistance;
1527
- this.camera.lookAt(this.playerCollider.end);
1498
+ this.camera.lookAt(this.playerPosition);
1528
1499
  }
1529
1500
  // dev 调试模式:在相机本地坐标系叠加偏移(向后上方)
1530
1501
  if (this.dev && !this.thirdPerson) {
@@ -1543,7 +1514,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1543
1514
  // xsh: direction prop → _localRayDirs(归一化)
1544
1515
  // xsh: raycaster.far = getDirection().length() → HORIZONTAL_FAR / downFar / upFar
1545
1516
  // xsh: Group3D rotation={x:-euler.x} → yawQuat(pitch 补偿)
1546
- // xsh: getIntersetResult(positionIncremental) → worldPos = playerCollider.end + positionIncremental
1517
+ // xsh: getIntersetResult(positionIncremental) → worldPos = playerPosition + positionIncremental
1547
1518
  //
1548
1519
  // 现行 10 条布局逐条表 = 上方「★ 矩形射线法 ★」权威规格块(唯一数据源,不在这里重复一遍)。
1549
1520
  /** 输出一条碰撞日志(唯一出口 = 浏览器控制台;皇上:「日志就是浏览器的控制台」) */
@@ -1579,18 +1550,16 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1579
1550
  // · 竖向保留 faceUp / 天花板三道闸守卫(「掉出世界」血案修复)。
1580
1551
  // · ⭐ 2026-10-11 A 档:**只沿「欠距最大那一面」的法线**推,且最后一格按欠距截短
1581
1552
  // (xsh 是每条命中各推一整格 ⇒ 4 条命中同一面墙一轮推 4cm,退出时必过冲)。
1582
- // 原 deficit / 票面合成(finalDir) / 逐格推离 / 白推保险丝 / 胶囊兜底机器整条拆除。
1553
+ // 原 deficit / 票面合成(finalDir) / 逐格推离 / 白推保险丝等旧分支已拆除。
1583
1554
  // ⚠️ 措辞校正(10-11):上面这句里「deficit」那半已随 A 档**回归**(`_horizFitDeficit()`:
1584
1555
  // 沿命中面法线的菱形可达距离 R·max(|n·x̂|,|n·ẑ|) 减当前垂距);
1585
- // 仍拆除的是「票面合成 finalDir / 白推保险丝 / 胶囊兜底」三件 —— 别照旧文案把它们捡回来。
1556
+ // 仍拆除的是「票面合成 finalDir / 白推保险丝」两件 —— 别照旧文案把它们捡回来。
1586
1557
  const positionIncremental = this._tempVec4.set(0, 0, 0);
1587
1558
  this._refreshInputDir(); // 台账 ix/iz 列还要用(与碰撞计算无关)
1588
1559
  const recordDebug = (this.dev || this.thirdPerson) && isFinalStep;
1589
1560
  let debugRecorded = !recordDebug;
1590
1561
  this._horizHit = false;
1591
- // ⭐ 皇上 2026-10-07 裁决「加一个每小帧轮数上限」:这一趟 while 最多走几轮就落地。
1592
- // 常态帧用旋钮 ROUNDS_PER_SUBSTEP(默认 500 与 MAX_ITERATIONS 同档 ⇒ 现状完全不变)。
1593
- const roundCap = this.ROUNDS_PER_SUBSTEP;
1562
+ this._horizContactCount = 0;
1594
1563
  const downRange = this.DOWN_RANGE;
1595
1564
  const upRange = this.UP_RANGE;
1596
1565
  let i = 0;
@@ -1604,7 +1573,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1604
1573
  // ⭐ 2026-10-02 模拟修正:真实 collider 此刻**还没被本步位移推过**,
1605
1574
  // 所以锚点 = 真实位置 + 本步位移 =「这一步走完会站在哪儿」的**模拟位置**
1606
1575
  // (数值上与旧写法「先 translate 再取 end」完全相同 ⇒ 几何、方向、测试基线都不变)。
1607
- const basePos = this._tempVec3.copy(this.playerCollider.end).add(this._subStep);
1576
+ const basePos = this._tempVec3.copy(this.playerPosition).add(this._subStep);
1608
1577
  // xsh: 射线原点 = 控制器位置 + positionIncremental(getIntersetResult(incremental) 语义)
1609
1578
  const worldPos = this._candPos.copy(basePos).add(positionIncremental);
1610
1579
  // xsh: Group3D rotation={x:-euler.x} 补偿 pitch → 只跟随 yaw
@@ -1664,17 +1633,21 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1664
1633
  }
1665
1634
  }
1666
1635
  if (bestDeficit > 1e-6) {
1667
- if (i > roundCap) {
1668
- // ⭐ **轮次上限**用完 ⇒ 这一轮起水平一格都不再推。
1669
- // 常态帧 = 皇上 10-07 点头新加的 ROUNDS_PER_SUBSTEP(默认 500 ⇒ 与旧行为同形)。
1670
- // ⚠️ 只拦水平:竖向(踩地/顶头)仍照常,否则人会往地心沉(皇上 09-25 冻结口径)。
1671
- } else {
1672
- // ⭐ 皇上 10-07 旨「每执行一次,偏移量是多少」⇒ 一格一行、带累计的形态保留:
1673
- // 大欠距仍是一格一格往外走,只有收尾那一格按欠距截短。
1674
- positionIncremental.addScaledVector(this._horizNormal, Math.min(this.RETREAT_SKIN, bestDeficit));
1675
- goOnComputer = true;
1676
- this._horizHit = true;
1677
- }
1636
+ // 大欠距逐格推离,直到所有水平射线都脱离碰撞面。
1637
+ positionIncremental.addScaledVector(this._horizNormal, Math.min(this.RETREAT_SKIN, bestDeficit));
1638
+ // 一面墙在多轮迭代里会重复命中;只保留方向不同的接触面。
1639
+ let known = false;
1640
+ for (let ni = 0; ni < this._horizContactCount; ni++) {
1641
+ if (this._horizContactNormals[ni].dot(this._horizNormal) > 0.999) {
1642
+ known = true;
1643
+ break;
1644
+ }
1645
+ }
1646
+ if (!known && this._horizContactCount < this._horizContactNormals.length) {
1647
+ this._horizContactNormals[this._horizContactCount++].copy(this._horizNormal);
1648
+ }
1649
+ goOnComputer = true;
1650
+ this._horizHit = true;
1678
1651
  }
1679
1652
  // ========== 向下射线(索引 0 = upTodwomRay)==========
1680
1653
  const downRay = this._rays[0];
@@ -1789,7 +1762,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1789
1762
  // 第 11 帧眼落到板底之下 0.0135,上射线打到**板底(法线朝下)**,旧口径
1790
1763
  // `positionIncremental.y -= upPen` 一帧把人往下砸 **0.9865m** → 眼落到 −2.0,
1791
1764
  // 从此两条竖射线彻底够不着 → 自由落体到 **−13.161547** → 撞上 camera.y≤−10 的
1792
- // 掉出世界复位(`this.playerCollider.end.set(0, 1, 10)`)→ 「陷进地面很长一段
1765
+ // 掉出世界复位(玩家视点重置到 (0, 1, 10))→ 「陷进地面很长一段
1793
1766
  // 距离然后凭空回到原点」。这就是皇上看到的现象。
1794
1767
  // 新口径:**绝不往下顶**,一步顶到「脚底刚好落在这块面之上」(眼高 = 面 y +
1795
1768
  // downRange + SKIN)。顶完眼只比板底高一个 SKIN → 下一轮迭代下射线立刻
@@ -1886,7 +1859,7 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1886
1859
  this._subStep.z + corrZ
1887
1860
  );
1888
1861
  if (this._appliedMove.lengthSq() > 0) {
1889
- this.playerCollider.translate(this._appliedMove);
1862
+ this.playerPosition.add(this._appliedMove);
1890
1863
  }
1891
1864
  // 本步已消费:归零,面板那两颗直接调 playerCollisions 的按钮不会重复走一步
1892
1865
  this._subStep.set(0, 0, 0);
@@ -1903,9 +1876,14 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1903
1876
  // ⚠️ 方向与推离用的是**同一个** `_horizNormal`(水平半区那轮算好、就地复用),绝不另算一遍。
1904
1877
  // ⚠️ 竖向 y 一字不动(重力/跳跃照旧);观察模式(回弹预览)不改速度,口径与 10-07 一致。
1905
1878
  if (this._horizHit && !this.previewRetreat) {
1906
- const into = this.playerVelocity.dot(this._horizNormal);
1907
- if (into < 0) {
1908
- this.playerVelocity.addScaledVector(this._horizNormal, -into);
1879
+ // 墙角中两个约束面不一定在同一轮成为“欠距最大面”。依次投影两轮,
1880
+ // 让每个面都得到 v·n >= 0,同时保留不受任何墙面约束的切向速度。
1881
+ for (let pass = 0; pass < 2; pass++) {
1882
+ for (let ni = 0; ni < this._horizContactCount; ni++) {
1883
+ const normal = this._horizContactNormals[ni];
1884
+ const into = this.playerVelocity.dot(normal);
1885
+ if (into < 0) this.playerVelocity.addScaledVector(normal, -into);
1886
+ }
1909
1887
  }
1910
1888
  }
1911
1889
  // ⭐ 十旨:一帧一条的抖动台账。**只由子步循环的最后一步(isFinalStep)记** ——
@@ -1931,10 +1909,8 @@ export default class FPScontrol extends EventDispatcher<FPScontrolEventTypeMap>
1931
1909
  // 越界传送
1932
1910
  teleportPlayerIfOob = (): void => {
1933
1911
  if (this.camera.position.y <= -10) {
1934
- this.playerCollider.start.set(0, 0.35, 0);
1935
- this.playerCollider.end.set(0, 1, 10);
1936
- this.playerCollider.radius = 0.35;
1937
- this.camera.position.copy(this.playerCollider.end);
1912
+ this.playerPosition.set(0, 1, 10);
1913
+ this.camera.position.copy(this.playerPosition);
1938
1914
  }
1939
1915
  };
1940
1916
  // 更新
@@ -1,10 +1,12 @@
1
- import { Matrix4, Mesh, PlaneGeometry, ShaderMaterial } from "three";
2
- /** 装饰地板(随机渐变的深灰、不透明) */
1
+ import { Clock, BufferGeometry, Camera, Group, Material, Matrix4, Mesh, PlaneGeometry, Scene, ShaderMaterial, WebGLRenderer } from "three";
2
+ /** 装饰地板(科技感格子贴图 + 紫色扩散动画,半透明可透视外观) */
3
3
  export default class Ground1 extends Mesh {
4
4
  tempMatrix: Matrix4;
5
5
  constructor();
6
6
  renderOrder: number;
7
7
  geometry: PlaneGeometry;
8
8
  setSize(width: number, height: number): void;
9
+ clock: Clock;
10
+ onBeforeRender: (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void;
9
11
  material: ShaderMaterial;
10
12
  }
@@ -2,44 +2,39 @@
2
2
  * @Author: xiaosihan
3
3
  * @Date: 2022-09-20 23:06:00
4
4
  * @Last Modified by: xiaosihan
5
- * @Last Modified time: 2024-08-17 02:16:56
5
+ * @Last Modified time: 2026-10-12 00:00:00
6
6
  *
7
- * ⭐ 2026-09-20 皇上旨两改:
8
- * ① 原「科技感装饰地板」(三张格子贴图 + 紫色扩散动画 `#8D108E` + 格子线 `#263C56`)
9
- * → 深灰地板(每帧 3 次贴图采样、贴图平铺无各向异性 → 摩尔纹 + home 页卡顿元凶之一)。
10
- * ② 皇上再旨「地板就是**随机渐变**的深灰色、**不透明**」:
11
- * → 深灰区间内用 **value noise 做随机渐变**(无贴图、零采样,天然无摩尔纹);
12
- * → `transparent: false`、alpha 恒 1(不透明)。
7
+ * ⭐ 装饰地板 = 科技感格子贴图 + 紫色扩散动画(2026-10-12 皇上旨「整条退回旧科技感地板」+「装饰地板需要透明」)。
8
+ * 历史:2026-09-20 皇上曾两旨把它改成「随机渐变的深灰、不透明」,理由是每帧 3 次贴图采样 +
9
+ * 平铺无各向异性 → 摩尔纹,且是 home 页卡顿元凶之一。**当年那个摩尔纹根因现在已经在
10
+ * `threeLoader.getTexture` 里根治了**(anisotropy=16 + generateMipmaps + LinearMipmapLinear),
11
+ * 所以贴图路线的重画成本比 09-20 那版低一档,本次按皇上旨意恢复旧外观。
13
12
  *
14
- * ⚠️ 不透明带来的变化与对策:
15
- * - 原靠「径向 alpha 淡出」柔化 500×500 地板的边界;不透明后改用**径向压暗**(乘 0.72~1.0)
16
- * 弱化硬边 —— 若哪天皇上要「边界完全消失」,只能改回 alpha 淡出(即恢复 transparent)。
17
- * - 调试线(FPScontrol)两条口径分家(2026-09-25 皇上逐条定案):**10 条射线辅助线开深度测试**,
18
- * 正是靠本地板「不透明 + depthWrite:true」才挡得住埋进地里的那半截线;**8/9 两条回弹向量线
19
- * 保持穿透显示**(关深度测试 + renderOrder:9999),照旧常显在最前。
20
- * ⚠️ 若哪天把本地板改回 transparent + depthWrite:false,射线辅助线会重新穿地浮出来。
13
+ * ⭐ 2026-10-12 皇上旨「装饰地板需要透明」⇒ 恢复旧版的**外观**口径(逐字照抄 09-20 之前的写法):
14
+ * - `transparent: true` + 径向 alpha 淡出 `mapColor.a = 1 - dist(center)*2`(中心实、外缘全透)。
15
+ * - **但 `depthWrite: true` 保留**(旧版本来就是 transparent + depthWrite:true):主渲染的深度缓冲
16
+ * 照写 ⇒ FPScontrol 那 10 条射线辅助线仍被地板挡住埋进地里的那半截(09-25 定案口径不受影响)。
21
17
  *
22
- * ⚠️ 改动红线(这几条被别处依赖,**不许顺手改**):
23
- * - `userData.fpsCollision = true`:FPScontrol 把地板当碰撞体(见 FPScontrol.ts 地面处理段)。
24
- * - ⚠️ 2026-10-11 皇上旨「物体的深度都要进深度图」⇒ 原第三条红线 `userData.aoFloor = true`
25
- * (把地板从可见深度图里排除的那条例外)**整条拆除**:它的前提是地板半透明可透视,与本文件
26
- * ② 的「不透明改造」自相矛盾。地板现在按普通实心面写进深度图,被它挡住的描边/辉光/覆盖色/
27
- * 磨砂会被裁掉。见 three-base/pass/ssao_pass.ts 文件头。
28
- * - ShaderMaterial 必须含 logdepthbuf chunk;linear 输出必须 `#include <colorspace_fragment>`;
29
- * 否则末端 sRGB 编码会二次编码 → 发白。
18
+ * ⭐ 2026-10-18 皇上旨「所有的对象都要进入到深度图里面了,不需要用参数过滤掉了」⇒ 当年为「可透视
19
+ * 地板」加的 `userData.aoFloor`(把地板从后处理的可见深度图里排除)连同 ssao_pass 里那整条过滤判据
20
+ * 一并删除。现在地板两张深度图都写 ⇒ 透过地板看到的实体,其描边/辉光/覆盖色/磨砂会被地板裁掉,
21
+ * 包括外缘 alpha≈0 那圈「看得见空、看不见地板」的区域。判据出处 three-base/pass/ssao_pass.ts 文件头,
22
+ * 护栏 test/depthDebug.test.js J 组。
30
23
  */
31
- import { Color, DoubleSide, Matrix4, Mesh, PlaneGeometry, ShaderChunk, ShaderMaterial } from "three";
24
+ import { Clock, Color, DoubleSide, Matrix4, Mesh, PlaneGeometry, ShaderChunk, ShaderMaterial, Vector2 } from "three";
32
25
  import { MathUtils } from "three";
33
26
  const { degToRad } = MathUtils;
34
- /** 装饰地板(随机渐变的深灰、不透明) */
27
+ import floorMap1PNG from "./floorMap1.png";
28
+ import floorMap2PNG from "./floorMap2.png";
29
+ import floorMap3PNG from "./floorMap3.png";
30
+ import threeLoader from "../threeLoader";
31
+ /** 装饰地板(科技感格子贴图 + 紫色扩散动画,半透明可透视外观) */
35
32
  export default class Ground1 extends Mesh {
36
33
  tempMatrix = new Matrix4();
37
34
  constructor() {
38
35
  super();
39
36
  // 标记为自己参与 FPS 碰撞检测
40
37
  this.userData.fpsCollision = true;
41
- // ⚠️ 这里原来还有 `this.userData.aoFloor = true;`(把本地板从可见深度图里排除的特例标记),
42
- // 2026-10-11 皇上旨「物体的深度都要进深度图」⇒ 连同 ssao_pass 的特例分支一起整条删除。
43
38
  }
44
39
  renderOrder = -1;
45
40
  geometry = (() => {
@@ -51,14 +46,22 @@ export default class Ground1 extends Mesh {
51
46
  setSize(width, height) {
52
47
  this.scale.set(width, 1, height);
53
48
  }
49
+ clock = new Clock();
50
+ onBeforeRender = (renderer, scene, camera, geometry, material, group) => {
51
+ const elapsedTime = this.clock.getElapsedTime();
52
+ this.material.uniforms.speed.value = (elapsedTime * 0.1) % 0.6;
53
+ this.material.uniforms.speed2.value = ((elapsedTime * 0.1) % 0.6) - 0.1;
54
+ };
54
55
  material = new ShaderMaterial({
55
56
  uniforms: {
56
- /** 随机渐变的**暗端**深灰(three 的 Color 存 linear,末端 colorspace_fragment 编码回 sRGB) */
57
- colorDark: { value: new Color("#2A2E33") },
58
- /** 随机渐变的**亮端**深灰 */
59
- colorLight: { value: new Color("#3D434B") },
60
- /** 低频噪声格数(越大斑块越碎;9 → 500×500 地板上每块约 55m 的大块渐变) */
61
- noiseScale: { value: 9 },
57
+ size: { value: new Vector2(10, 10) }, // 格子平铺格数
58
+ speed: { value: 0 }, // 扩散值0-1
59
+ speed2: { value: 0 }, // 扩散值0-1
60
+ speedColor: { value: new Color("#8D108E") }, // 格子扩散颜色
61
+ lineColor: { value: new Color("#263C56") }, // 格子线的颜色
62
+ map1: { value: threeLoader.getTexture(floorMap1PNG) }, // 贴图纹理
63
+ map2: { value: threeLoader.getTexture(floorMap2PNG) }, // 贴图纹理
64
+ map3: { value: threeLoader.getTexture(floorMap3PNG) }, // 贴图纹理
62
65
  },
63
66
  vertexShader: `
64
67
  ${ShaderChunk.common}
@@ -72,42 +75,59 @@ export default class Ground1 extends Mesh {
72
75
  `,
73
76
  fragmentShader: `
74
77
  ${ShaderChunk.logdepthbuf_pars_fragment}
75
- uniform vec3 colorDark;
76
- uniform vec3 colorLight;
77
- uniform float noiseScale;
78
+ uniform sampler2D map1;
79
+ uniform sampler2D map2;
80
+ uniform sampler2D map3;
81
+ uniform vec2 size;
82
+ uniform vec3 lineColor;
83
+ uniform float speed;
84
+ uniform float speed2;
85
+ uniform vec3 speedColor;
78
86
  varying vec2 vUv;
87
+ void main() {
79
88
 
80
- // 伪随机 hash → value noise:无贴图、零采样,天然没有平铺摩尔纹
81
- float hash21(vec2 p) {
82
- return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
83
- }
84
- float valueNoise(vec2 p) {
85
- vec2 i = floor(p);
86
- vec2 f = fract(p);
87
- vec2 u = f * f * (3.0 - 2.0 * f); // smoothstep 插值 → 柔和渐变,不是硬格子
88
- return mix(mix(hash21(i), hash21(i + vec2(1.0, 0.0)), u.x),
89
- mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0, 1.0)), u.x), u.y);
90
- }
89
+ // 中心点
90
+ vec2 center = vec2(0.5,0.5);
91
91
 
92
- void main() {
93
- // 两个八度叠加:低频大块渐变 + 中频细节 → 深灰区间内的**随机渐变**
94
- float n = valueNoise(vUv * noiseScale) * 0.65
95
- + valueNoise(vUv * noiseScale * 3.1) * 0.35;
96
- vec3 col = mix(colorDark, colorLight, clamp(n, 0.0, 1.0));
92
+ vec4 map1Color = texture2D(map1, fract( vUv * size) );
93
+
94
+ // 格子中心渐变颜色
95
+ if(distance( vUv , center ) < speed){
96
+ map1Color = mix(map1Color, vec4(speedColor, 1.0), clamp((1.0 - (distance(distance( vUv , center ), speed) * 8.0) ) * map1Color.a, 0.0, 1.0 ) );
97
+ }
98
+
99
+ vec4 map2Color = texture2D(map2, fract( vUv * size) );
100
+
101
+ vec4 map3Color = texture2D(map3, fract( vUv * size) );
102
+
103
+ // 正方形装饰中心渐变颜色
104
+ if(distance( vUv , center ) < speed2){
105
+ map3Color = mix(map3Color, map3Color * 3.0, clamp((1.0 - (distance(distance( vUv , center ), speed) * 4.0) ) * map3Color.a, 0.0, 1.0 ) );
106
+ }
107
+
108
+ vec4 mapColor = mix(map1Color, map2Color, map2Color.a);
109
+
110
+ mapColor = mix(mapColor, map3Color, map3Color.a);
111
+
112
+ //添加背景色
113
+ mapColor = mix(mapColor, vec4(lineColor.rgb, 1.0 ), 1.0 - mapColor.a);
114
+
115
+ // 设置透明度(中心实、外缘全透的径向淡出 —— 500×500 大地板的硬边靠它消失)
116
+ mapColor.a = 1.0 - (distance( vUv , center ) * 2.0);
97
117
 
98
- // ⚠️ 不透明地板无法用 alpha 淡出边界 → 改用径向压暗弱化 500×500 的硬边
99
- float edge = 1.0 - smoothstep(0.34, 0.5, distance(vUv, vec2(0.5, 0.5)));
100
- col *= mix(0.72, 1.0, edge);
118
+ gl_FragColor = mapColor;
101
119
 
102
- gl_FragColor = vec4(col, 1.0); // 不透明
103
120
  ${ShaderChunk.logdepthbuf_fragment}
104
121
  #include <colorspace_fragment>
105
122
  }
106
123
  `,
107
124
  depthTest: true,
108
125
  depthWrite: true,
109
- // ⭐ 皇上 2026-09-20 旨:地板**不透明**
110
- transparent: false,
126
+ // ⭐ 皇上 2026-10-12 旨「装饰地板需要透明」:恢复旧版 transparent:true(半透可透视)。
127
+ // ⚠️ depthWrite 保持 true —— 主渲染的深度缓冲照写,FPScontrol 的 10 条射线辅助线才挡得住
128
+ // 埋进地里的那半截(09-25 定案)。后处理特效的遮挡判定走 baseDepthTexture,2026-10-18
129
+ // 皇上旨「所有的对象都要进入到深度图里面了」⇒ 地板也写进那张图,会裁掉身后特效。
130
+ transparent: true,
111
131
  side: DoubleSide,
112
132
  });
113
133
  }