three-base 2.0.169 → 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 (124) hide show
  1. package/FPScontrol.d.ts +55 -30
  2. package/FPScontrol.js +278 -225
  3. package/FPScontrol.ts +177 -127
  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 +8 -3
  10. package/MktMesh/MktMesh.js +43 -14
  11. package/MktMesh/MktMesh.ts +40 -14
  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/MktMesh/types.d.ts +5 -1
  25. package/MktMesh/types.ts +5 -1
  26. package/PipeMesh/PipeJoint.d.ts +22 -0
  27. package/PipeMesh/PipeJoint.js +33 -0
  28. package/PipeMesh/PipeJoint.ts +38 -0
  29. package/PipeMesh/PipeMesh.d.ts +94 -22
  30. package/PipeMesh/PipeMesh.js +192 -53
  31. package/PipeMesh/PipeMesh.ts +223 -76
  32. package/PipeMesh/PipeStraight.d.ts +19 -0
  33. package/PipeMesh/PipeStraight.js +34 -0
  34. package/PipeMesh/PipeStraight.ts +45 -0
  35. package/PipeMesh/axisSnap.d.ts +52 -0
  36. package/PipeMesh/axisSnap.js +52 -0
  37. package/PipeMesh/axisSnap.ts +93 -0
  38. package/PipeMesh/directRoute.d.ts +85 -0
  39. package/PipeMesh/directRoute.js +126 -0
  40. package/PipeMesh/directRoute.ts +190 -0
  41. package/PipeMesh/index.d.ts +6 -0
  42. package/PipeMesh/index.js +6 -0
  43. package/PipeMesh/index.ts +7 -1
  44. package/PipeMesh/pipeMaterial.d.ts +16 -0
  45. package/PipeMesh/pipeMaterial.js +23 -0
  46. package/PipeMesh/pipeMaterial.ts +26 -0
  47. package/ThreeBase.d.ts +2 -49
  48. package/ThreeBase.js +43 -164
  49. package/ThreeBase.ts +1176 -1314
  50. package/instanced/InstancedBatch.d.ts +14 -1
  51. package/instanced/InstancedBatch.js +53 -3
  52. package/instanced/InstancedBatch.ts +62 -3
  53. package/instanced/InstancedChild.d.ts +24 -0
  54. package/instanced/InstancedChild.js +40 -0
  55. package/instanced/InstancedChild.ts +41 -0
  56. package/instanced/InstancedGroup.d.ts +48 -0
  57. package/instanced/InstancedGroup.js +45 -1
  58. package/instanced/InstancedGroup.ts +85 -0
  59. package/package.json +2 -7
  60. package/pass/bloom_pass.js +7 -14
  61. package/pass/bloom_pass.ts +7 -16
  62. package/pass/cover_pass.js +7 -15
  63. package/pass/cover_pass.ts +7 -17
  64. package/pass/debugDepthState.d.ts +16 -8
  65. package/pass/debugDepthState.js +4 -0
  66. package/pass/debugDepthState.ts +27 -9
  67. package/pass/glow_pass.js +19 -110
  68. package/pass/glow_pass.ts +19 -115
  69. package/pass/ssao_pass.d.ts +14 -59
  70. package/pass/ssao_pass.js +66 -171
  71. package/pass/ssao_pass.ts +63 -207
  72. package/pass/stroke_pass.js +13 -16
  73. package/pass/stroke_pass.ts +14 -18
  74. package/shaders/baseDepthVisShader.d.ts +1 -1
  75. package/shaders/baseDepthVisShader.js +1 -1
  76. package/shaders/baseDepthVisShader.ts +1 -1
  77. package/shaders/debugDepthCompareShader.d.ts +2 -2
  78. package/shaders/debugDepthCompareShader.js +2 -2
  79. package/shaders/debugDepthCompareShader.ts +2 -2
  80. package/shaders/debugDepthJudgeShader.d.ts +1 -1
  81. package/shaders/debugDepthJudgeShader.js +1 -1
  82. package/shaders/debugDepthJudgeShader.ts +1 -1
  83. package/PipeMesh/PathGeometry.d.ts +0 -44
  84. package/PipeMesh/PathGeometry.js +0 -307
  85. package/PipeMesh/PathGeometry.ts +0 -343
  86. package/PipeMesh/PathPoint.d.ts +0 -17
  87. package/PipeMesh/PathPoint.js +0 -31
  88. package/PipeMesh/PathPoint.ts +0 -36
  89. package/PipeMesh/PathPointList.d.ts +0 -31
  90. package/PipeMesh/PathPointList.js +0 -209
  91. package/PipeMesh/PathPointList.ts +0 -227
  92. package/PipeMesh/PathTubeGeometry.d.ts +0 -25
  93. package/PipeMesh/PathTubeGeometry.js +0 -130
  94. package/PipeMesh/PathTubeGeometry.ts +0 -146
  95. package/WaterFlow/FloatingDuck.d.ts +0 -88
  96. package/WaterFlow/FloatingDuck.js +0 -307
  97. package/WaterFlow/FloatingDuck.ts +0 -445
  98. package/WaterFlow/GPUFluidSimulation.d.ts +0 -56
  99. package/WaterFlow/GPUFluidSimulation.js +0 -785
  100. package/WaterFlow/GPUFluidSimulation.ts +0 -827
  101. package/WaterFlow/ReservoirSystem.d.ts +0 -106
  102. package/WaterFlow/ReservoirSystem.js +0 -621
  103. package/WaterFlow/ReservoirSystem.ts +0 -754
  104. package/WaterFlow/SPHField.d.ts +0 -35
  105. package/WaterFlow/SPHField.js +0 -204
  106. package/WaterFlow/SPHField.ts +0 -228
  107. package/WaterFlow/ScreenSpaceFluidPass.d.ts +0 -69
  108. package/WaterFlow/ScreenSpaceFluidPass.js +0 -736
  109. package/WaterFlow/ScreenSpaceFluidPass.ts +0 -789
  110. package/WaterFlow/ShallowWaterSurface.d.ts +0 -45
  111. package/WaterFlow/ShallowWaterSurface.js +0 -441
  112. package/WaterFlow/ShallowWaterSurface.ts +0 -510
  113. package/WaterFlow/WaterFlow.d.ts +0 -87
  114. package/WaterFlow/WaterFlow.js +0 -171
  115. package/WaterFlow/WaterFlow.ts +0 -219
  116. package/WaterFlow/types.d.ts +0 -161
  117. package/WaterFlow/types.js +0 -1
  118. package/WaterFlow/types.ts +0 -189
  119. package/WaterFlow/waterFlowPass.d.ts +0 -3
  120. package/WaterFlow/waterFlowPass.js +0 -20
  121. package/WaterFlow/waterFlowPass.ts +0 -40
  122. package/shaders/debugExcludedShader.d.ts +0 -13
  123. package/shaders/debugExcludedShader.js +0 -46
  124. package/shaders/debugExcludedShader.ts +0 -48
@@ -13,8 +13,8 @@
13
13
  * ⇒ 收窄上面那句「如果没有目标全景…就留空」的适用边界:**留空只发生在「项目里的全景总数不够一格一人」时**;
14
14
  * 序号越界的那个位置根本不是一张全景 ⇒ 跳过它、继续往还有货的那一侧扩,槽位不许白留给「不存在的序号」。
15
15
  * 同一道旨还要求:新加的全景点**不必等中心换人**才进 shader ⇒ 中心没换人时在册的人照旧一格不动,空格当场补满。
16
- * ⚠️ 上限仍是 `MKT_PANO_MAX_SLOTS`(=8):8 点 × 左右 2 张拼合槽 = 16 个 sampler,那是 GLSL 物理闸
17
- * (皇上机器 maxTextures=16)⇒ 本函数不越那道闸,只是把那 8 格**尽量用满**。
16
+ * ⚠️ 上限仍是 `MKT_PANO_MAX_SLOTS`(=5):5 点 × 左中右 3 张拼合槽 = 15 个 sampler,那是 GLSL 物理闸
17
+ * (皇上机器 maxTextures=16)⇒ 本函数不越那道闸,只是把那 5 格**尽量用满**。
18
18
  * ⭐ 皇上旨 2026-10-07 第二十一道旨(verbatim):「判断使用那些全景球的判断条件, 从距离相机的距离 改成
19
19
  * 判断距离控制器的中心点的距离来判断」⇒ **本函数的尺子另一端换了人**:函数体一个字不改(它只吃调用方算好的
20
20
  * `d`),改的是**调用方**——编辑态与预览的锚点 = 轨道控制器注视点 `controls.target`(画面正中央那个点),
@@ -44,9 +44,11 @@
44
44
  * ⇒ 本函数把 09-27 那半条**作废**:不再取「最近 4 个」,改取「最近那一张的序号邻居」,槽号也不再按距离排。
45
45
  * - 09-26 的「一进一出 + 0.5m 死区」精神保留,但对象从「谁挤掉谁」变成「谁能抢走中心」。
46
46
  *
47
- * ⚠️ 为什么 0 号槽必须是中心:立方体(`FurnitureObject`)那份 `BoxGeometry` **没有** `aPanoIndex` attribute ⇒
48
- * 序号映射 shader 里 `vPanoIndex` 恒 0 ⇒ 立方体显示的就是名册第 0 格那一张(皇上旨 10-02「模型也按距离相机最近的方式
49
- * 设置全景参数」的落点)。中心坐别的格 = 立方体显示错图。
47
+ * ⚠️ 为什么 0 号槽必须是中心:⭐ 10-02 那档口径下立方体(`FurnitureObject`)那份 `BoxGeometry` **没有**
48
+ * `aPanoIndex` attribute ⇒ 序号映射 shader 里 `vPanoIndex` 恒 0 ⇒ 全体立方体显示的都是名册第 0 格那一张
49
+ * (中心坐别的格 = 立方体显示错图)。⭐ 皇上旨 2026-10-12 之后立方体**逐颗自带序号**
50
+ * (`InstancedChild.panoIndex` → 逐实例 `aPanoIndex`),这条不再是「立方体显示哪张」的依据;
51
+ * 0 号槽 = 中心照旧(它就是离锚点最近那一张,名册其余格按它的列表邻居外扩)。
50
52
  *
51
53
  * ⚠️ 为什么单独成文件:本函数零依赖(不碰 three / MobX / DOM),`test/mktmesh.test.js` 的 **AP 组**会用 esbuild
52
54
  * 把**这份源码原文**转译后真跑一遍(中心/邻居/外扩/越界不占格/死区/删点/幂等/空格回填 各断言一次)。
@@ -64,8 +66,8 @@ export const PANO_SLOT_SWAP_MARGIN_M = 0.5;
64
66
  * 算出下一帧的槽位名册(**不修改传进来的 roster**,返回新数组)。
65
67
  * @param roster 当前名册(0 号槽 = 上一帧的中心),长度可以小于 maxSlots(内部按 undefined 补齐)
66
68
  * @param dists 本帧全部全景点及其到**锚点**的距离(锚点 = 调用方那把尺子的端点;⭐ 10-07 第二十一道旨后 = 控制器中心点),**数组顺序 = 全景列表顺序**(下标就是旨意里说的「几号全景」)
67
- * @param maxSlots shader 的槽位上限(= `MKT_PANO_MAX_SLOTS`;⭐ 2026-10-06 晚旨后是 **8**:8 点 × 每个全景
68
- * 左右 2 张 RT = 16 sampler 仍是 GLSL 物理上限。⛔ 本函数不许假设它恒等于 4)
69
+ * @param maxSlots shader 的槽位上限(= `MKT_PANO_MAX_SLOTS`;⭐ 2026-10-23 旨后是 **5**:5 点 × 每个全景
70
+ * 左中右 3 张 RT = 15 sampler,GLSL 物理上限仍是 16。⛔ 本函数不许假设它恒等于 4 或 8)
69
71
  * @param margin 抢中心的死区(米,= `PANO_SLOT_SWAP_MARGIN_M`)
70
72
  * @returns 长度恒 = maxSlots 的新名册:**0 号槽 = 中心**,其余槽 = 列表里按序号一层层往外扩的那些
71
73
  * (同一层先取离锚点更近那侧)。⭐ 10-07 旨「尽可能填满」⇒ 越界的序号不占格、外扩继续往有货那侧走,
@@ -13,8 +13,8 @@
13
13
  * ⇒ 收窄上面那句「如果没有目标全景…就留空」的适用边界:**留空只发生在「项目里的全景总数不够一格一人」时**;
14
14
  * 序号越界的那个位置根本不是一张全景 ⇒ 跳过它、继续往还有货的那一侧扩,槽位不许白留给「不存在的序号」。
15
15
  * 同一道旨还要求:新加的全景点**不必等中心换人**才进 shader ⇒ 中心没换人时在册的人照旧一格不动,空格当场补满。
16
- * ⚠️ 上限仍是 `MKT_PANO_MAX_SLOTS`(=8):8 点 × 左右 2 张拼合槽 = 16 个 sampler,那是 GLSL 物理闸
17
- * (皇上机器 maxTextures=16)⇒ 本函数不越那道闸,只是把那 8 格**尽量用满**。
16
+ * ⚠️ 上限仍是 `MKT_PANO_MAX_SLOTS`(=5):5 点 × 左中右 3 张拼合槽 = 15 个 sampler,那是 GLSL 物理闸
17
+ * (皇上机器 maxTextures=16)⇒ 本函数不越那道闸,只是把那 5 格**尽量用满**。
18
18
  * ⭐ 皇上旨 2026-10-07 第二十一道旨(verbatim):「判断使用那些全景球的判断条件, 从距离相机的距离 改成
19
19
  * 判断距离控制器的中心点的距离来判断」⇒ **本函数的尺子另一端换了人**:函数体一个字不改(它只吃调用方算好的
20
20
  * `d`),改的是**调用方**——编辑态与预览的锚点 = 轨道控制器注视点 `controls.target`(画面正中央那个点),
@@ -44,9 +44,11 @@
44
44
  * ⇒ 本函数把 09-27 那半条**作废**:不再取「最近 4 个」,改取「最近那一张的序号邻居」,槽号也不再按距离排。
45
45
  * - 09-26 的「一进一出 + 0.5m 死区」精神保留,但对象从「谁挤掉谁」变成「谁能抢走中心」。
46
46
  *
47
- * ⚠️ 为什么 0 号槽必须是中心:立方体(`FurnitureObject`)那份 `BoxGeometry` **没有** `aPanoIndex` attribute ⇒
48
- * 序号映射 shader 里 `vPanoIndex` 恒 0 ⇒ 立方体显示的就是名册第 0 格那一张(皇上旨 10-02「模型也按距离相机最近的方式
49
- * 设置全景参数」的落点)。中心坐别的格 = 立方体显示错图。
47
+ * ⚠️ 为什么 0 号槽必须是中心:⭐ 10-02 那档口径下立方体(`FurnitureObject`)那份 `BoxGeometry` **没有**
48
+ * `aPanoIndex` attribute ⇒ 序号映射 shader 里 `vPanoIndex` 恒 0 ⇒ 全体立方体显示的都是名册第 0 格那一张
49
+ * (中心坐别的格 = 立方体显示错图)。⭐ 皇上旨 2026-10-12 之后立方体**逐颗自带序号**
50
+ * (`InstancedChild.panoIndex` → 逐实例 `aPanoIndex`),这条不再是「立方体显示哪张」的依据;
51
+ * 0 号槽 = 中心照旧(它就是离锚点最近那一张,名册其余格按它的列表邻居外扩)。
50
52
  *
51
53
  * ⚠️ 为什么单独成文件:本函数零依赖(不碰 three / MobX / DOM),`test/mktmesh.test.js` 的 **AP 组**会用 esbuild
52
54
  * 把**这份源码原文**转译后真跑一遍(中心/邻居/外扩/越界不占格/死区/删点/幂等/空格回填 各断言一次)。
@@ -72,8 +74,8 @@ export interface PanoRosterCand<T> {
72
74
  * 算出下一帧的槽位名册(**不修改传进来的 roster**,返回新数组)。
73
75
  * @param roster 当前名册(0 号槽 = 上一帧的中心),长度可以小于 maxSlots(内部按 undefined 补齐)
74
76
  * @param dists 本帧全部全景点及其到**锚点**的距离(锚点 = 调用方那把尺子的端点;⭐ 10-07 第二十一道旨后 = 控制器中心点),**数组顺序 = 全景列表顺序**(下标就是旨意里说的「几号全景」)
75
- * @param maxSlots shader 的槽位上限(= `MKT_PANO_MAX_SLOTS`;⭐ 2026-10-06 晚旨后是 **8**:8 点 × 每个全景
76
- * 左右 2 张 RT = 16 sampler 仍是 GLSL 物理上限。⛔ 本函数不许假设它恒等于 4)
77
+ * @param maxSlots shader 的槽位上限(= `MKT_PANO_MAX_SLOTS`;⭐ 2026-10-23 旨后是 **5**:5 点 × 每个全景
78
+ * 左中右 3 张 RT = 15 sampler,GLSL 物理上限仍是 16。⛔ 本函数不许假设它恒等于 4 或 8)
77
79
  * @param margin 抢中心的死区(米,= `PANO_SLOT_SWAP_MARGIN_M`)
78
80
  * @returns 长度恒 = maxSlots 的新名册:**0 号槽 = 中心**,其余槽 = 列表里按序号一层层往外扩的那些
79
81
  * (同一层先取离锚点更近那侧)。⭐ 10-07 旨「尽可能填满」⇒ 越界的序号不占格、外扩继续往有货那侧走,
@@ -1,11 +1,14 @@
1
1
  import { Texture, type WebGLRenderer } from "three";
2
+ import { type TileLayout } from "./mktShader";
2
3
  /**
3
4
  * 拼合器对外接口(消费方 `MktMesh` / 编辑器渲染器 / 预览只认这一个类)。
4
5
  * 10-06 之前它是「两份实现共用一份接口」里的 canvas 那一份 ⇒ 那份已奉旨删除,接口留在原地当文档。
5
6
  */
6
7
  export interface PanoStitcher {
7
- /** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做) */
8
- ensure(urls: string[]): void;
8
+ /** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
9
+ * 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 3×3 / 3 槽 / 4096×6144,10-23 旨);
10
+ * 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。 */
11
+ ensure(urls: string[], layout?: TileLayout): void;
9
12
  /**
10
13
  * 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
11
14
  * ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
@@ -55,7 +58,8 @@ export declare class PanoTileStitcher implements PanoStitcher {
55
58
  private _scene;
56
59
  private _camera;
57
60
  private _mat;
58
- /** 几何是 2×2(铺满正交相机的 [-1,1]²),每格只把它缩到 1/4 再挪过去 ⇒ 一格 = 槽的 1/16 */
61
+ /** 几何是 2×2(铺满正交相机的 [-1,1]²),每格按 `1/slotCellCols × 1/slotCellRows` 缩放再挪过去
62
+ * ⇒ 全景一格 = 槽的 1/3(横 1 竖 3,10-23 旨),图片平面一格 = 1/4(横 2 竖 2) */
59
63
  private _quad;
60
64
  /** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
61
65
  private _blank;
@@ -63,13 +67,13 @@ export declare class PanoTileStitcher implements PanoStitcher {
63
67
  private _rtTextures;
64
68
  constructor();
65
69
  /** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
66
- * ⭐ 09-29 晚旨「不足的涂灰」+ 10-06 晚旨「8×4 = 32 片」:1~32 条皆可入组,内部补空串占位到 32;
70
+ * ⭐ 09-29 晚旨「不足的涂灰」+ 10-23 旨「3×3 = 9 片」:1~9 条皆可入组,内部补空串占位到 9;
67
71
  * 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
68
- * ⛔ 旧版 16 条(4×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
69
- * 64 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
70
- * ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null,null]` —— 槽数再变会漏改)。 */
71
- ensure(urls: string[]): void;
72
- /** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 16 格画满)。
72
+ * ⛔ 旧版 16 条(4×4)、32 条(8×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
73
+ * 超 9 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
74
+ * ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null]` —— 槽数再变会漏改)。 */
75
+ ensure(urls: string[], layout?: TileLayout): void;
76
+ /** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
73
77
  * RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
74
78
  peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null;
75
79
  /** ⭐ 这条路交出去的槽纹理本来就是 GL 渲染目标的附件 —— 像素已经在显存里,没有「上传」这一步。
@@ -116,8 +120,9 @@ export declare class PanoTileStitcher implements PanoStitcher {
116
120
  * 否则会对已释放纹理 `initTexture` ⇒ 凭空把显存建回来且无人回收。
117
121
  * (与 `MktMesh._uploadTileTexture` 的「排上队之后已被清掉 ⇒ 本项作废」同一口径。) */
118
122
  private _uploadTile;
119
- /** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(16 格里的一格)。
120
- * ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 16 帧**,
123
+ /** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(每槽 `slotCellCols × slotCellRows` 格里的一格,
124
+ * 10-23 旨后全景是「横 1 × 竖 3」= 3 格、图片平面是 2×2 = 4 格)。
125
+ * ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 cellsPerSlot 帧**,
121
126
  * 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
122
127
  private _stitchCell;
123
128
  /** 把整张 RT 涂成缺失灰:RT 建好后的第一件事 ⇒ 保证「画不满也先喂给 shader」时**没有未定义像素**。
@@ -135,8 +140,10 @@ export declare class PanoTileStitcher implements PanoStitcher {
135
140
  /** 换 renderer:RT 是旧上下文的私有资源,留着既画不了也释放不掉 ⇒ 在册组整份作废,
136
141
  * 靠消费方每帧照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存,不重新编码)。 */
137
142
  private _switchRenderer;
138
- /** 懒建某槽的渲染目标:4096×4096 RGBA8 + sRGB + mipmap,并**立刻整张涂缺失灰**
139
- * (⛔ 未定义像素不许进 shader:`peek()` 现在画过 1 格就交出去)。 */
143
+ /** 懒建某槽的渲染目标:尺寸取自 `g.layout.rtW/rtH`(全景档 = **4096×6144**,10-23 旨)、RGBA8 + sRGB + mipmap,
144
+ * 并**立刻整张涂缺失灰**(⛔ 未定义像素不许进 shader:`peek()` 现在画过 1 格就交出去)。
145
+ * ⚠️ 6144 这个数受本机 `MAX_TEXTURE_SIZE` / 最大渲染目标尺寸约束(ANGLE/D3D11 的 11_0 是 16384,远超;
146
+ * 只有老 Intel 集显才可能卡 4096 —— 真建不出来 three 会报 `GL_INVALID_VALUE`,届时墙上整片缺失灰)。 */
140
147
  private _ensureRt;
141
148
  /** 释放一片瓦片的纹理 + ImageBitmap(它只属于一格,画完即弃 ⇒ 显存峰值 = 一格) */
142
149
  private _dropTile;
@@ -1,5 +1,5 @@
1
1
  /*
2
- * @Description: 32 瓦片全景的「离屏拼合器」= **shader 逐格画进 WebGLRenderTarget**(本项目唯一一份拼合实现)。
2
+ * @Description: 9 瓦片全景的「离屏拼合器」= **shader 逐格画进 WebGLRenderTarget**(本项目唯一一份拼合实现)。
3
3
  *
4
4
  * 三道旨意叠成现在这一份:
5
5
  * ① 2026-09-29:一张全景切多片(每片所有 GPU 都装得下),运行时离屏拼回若干张固定尺寸的「槽纹理」,
@@ -18,54 +18,66 @@
18
18
  * ⇒ 旨②的 8×8=64 片 / 2×2 四槽 **作废**:改 **8×4=32 片 / 左右 2 槽**(槽纹理跟着变 4096² 正方形)。
19
19
  * ⭐ 净效果:sampler 总数仍 16(硬顶)⇒ 同屏全景数 **4 → 8**;画质与单全景显存**分毫未动**;
20
20
  * 单全景下载片数 64 → 32(**减半**)。旧存档 64 条地址**不兼容**,皇上「有异常我会重传」。
21
+ * ④ 2026-10-23(皇上旨,逐字):「瓦片拆成 9 个了,每个瓦片的尺寸是 4096 * 2048 所有,全景的 RT 由原来的
22
+ * 2 个分成 3 个,每个 RT 的尺寸是 4096 * 6144,竖直排列 3 个瓦片」
23
+ * ⇒ 改 **3×3=9 片 / 按列 3 槽**,每槽 = 原图一整列(横 1 格 × 竖 3 格)= **4096×6144**、与原图 1:1(不再缩小)。
24
+ * ⚠️ 代价(皇上在「A/B/C/D」里点了 **A**):sampler = 同屏景点数 × 3,硬顶 16 ⇒ **同屏全景点数 8 → 5**(5×3=15)。
25
+ * 单全景 RT 显存 170MB → **288MB**(含 mipmap ≈384MB)。旧存档 32 条地址**不兼容**,「有异常我会重传」。
21
26
  *
22
27
  * ⚠️ **仍然有 canvas,但只在上传切图那一步**(`src/views/MktMeshEdit/panoramaUpload.ts` 用 2D 画布把原图
23
- * 切成 32 个 JPEG 传 OSS)—— 那是**离线、一次性、产出的是数据**;运行时这条路上一个 canvas 都没有。
28
+ * 切成 9 个 JPEG 传 OSS)—— 那是**离线、一次性、产出的是数据**;运行时这条路上一个 canvas 都没有。
24
29
  *
25
30
  * ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
26
- * 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 32 条 XHR 一起炸。
31
+ * 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
27
32
  * 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
28
33
  * 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
29
34
  * 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
30
35
  * 到 GPU 里面去」)。这一趟**只上传、不画** ⇒ 上传与绘制永不落在同一拍(09-29 那句「两个大头别在同一
31
36
  * 帧」的延续),而且第 2 步天然只能画「上一帧及更早」传上去的图 ⇒ 每帧 = 1 上传 + 1 绘制(稳态)。
32
37
  *
33
- * ⚠️ 于是「一帧一片」从最旧版的 16 帧/组变成 **32 帧/组**(30ms/帧 ⇒ 全量到位约 1.0s,最旧版约 0.5s;
34
- * 10-06 白天那版 64 片是 64 帧/组 ≈ 1.9s,现砍半)。每格像素 1024²(旧 1024×512)。
35
- * 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等 16 帧。
38
+ * ⚠️ 于是「一帧一片」的帧数随网格走:16 片 → 32 片(30ms/帧 ⇒ 全量到位约 1.0s)→ **9 片/组 = 9 帧**(约 0.27s,
39
+ * 10-23 旨)。每格像素 = `RT_STITCH_SLOT_W/1 × RT_STITCH_SLOT_H/3` = **4096×2048**(原图切出来那片多大就多大,一比一)。
40
+ * 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等 9 帧。
36
41
  *
37
- * ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的 16 格画满,
42
+ * ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的 `cellsPerSlot` 格画满,
38
43
  * **画过 ≥1 格就交出去**。为了保证交出去的那张 RT 里绝无未定义像素,RT 建好后的第一件事是
39
44
  * 整张涂成缺失灰(`_clearRt`)⇒ 观感 = 该象限从左上角起逐格显影,缺数据的格子一直是灰的。
40
45
  *
41
46
  * ── 画质四条(⛔ 改任何一条都是改画质,先请旨)──────────────────────────────
42
- * 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **4096×4096 正方形**、每边 4 格、原图**横 8 × 竖 4** 片,
43
- * 全在 `./mktShader`(单一来源)。缩放比 (W/8)/1024 与 16 片时代 (W/4)/2048 **是同一个数**
44
- * ⇒ 这次切细不涨不降清晰度。⛔ 槽必须是正方形:每槽现在覆盖「横 180° × 竖 180°」,
45
- * 沿用 4096×2048 会让纵向被压 2 倍(见 `RT_STITCH_SLOT_H` 那条注释)。
47
+ * 1. **尺寸 / 格子几何**:槽 `RT_STITCH_SLOT_W/H` = **4096×6144**、每槽「横 1 格 × 竖 3 格」、原图**横 3 × 竖 3** 片,
48
+ * 全在 `./mktShader`(单一来源)。⭐ 10-23 旨后格子尺寸 = 那片瓦片的尺寸 ⇒ **一比一**:11968×5984 那类原图
49
+ * 切 3×3 得 ~3989×1994 的片,铺进 4096×2048 的格子几乎不缩(缩放比 0.97,上一版 8×4 是 1.46 = 缩小)
50
+ * ⇒ 单全景有效分辨率 33.5Mpx → **75.5Mpx**,是**升**画质,与「千万不要降低质量」红线同向。
51
+ * ⛔ 不许把槽改回 4096² 正方形:那是上一版「每槽覆盖横 180°×竖 180°」的产物;现口径每槽只覆盖**横 120° × 竖 180°**,
52
+ * 宽高比 2:3 必须与「原图一整列」一致,否则格子被强行拉伸 = 几何挤歪。
46
53
  * 2. **色彩**:瓦片纹理 `colorSpace = SRGBColorSpace` ⇒ WebGL2 内部格式 `SRGB8_ALPHA8`,采样时硬件解码成
47
54
  * linear;RT 纹理同样标 sRGB ⇒ 写入时硬件再编码回 sRGB。一进一出**逐字节还原**。
48
55
  * ⚠️ 另一半证据在 three 里:写进非 XR 的 render target 时片元末尾的 `linearToOutputTexel` 恒按
49
56
  * LinearSRGB(= 不转换),编码完全交给硬件 ⇒ ⛔ 我们的片元绝不 `#include <colorspace_fragment>`,
50
57
  * 也不带 tone mapping 的 chunk(那两处都会再转一遍 ⇒ 画面发白)。
51
58
  * 3. **摩尔纹**:两层都开 mipmap ——
52
- * ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:一片原图 1488px 缩进 1024px 的格子是
53
- * **缩小**,⛔ 单点双线性会采出锯齿(WebGL2 对 NPOT 也能生成 mipmap,three r163 起只支持 WebGL2)。
59
+ * ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:10-23 旨后一片原图(~3989×1994)铺进 4096×2048
60
+ * 的格子是**一比一**,但墙面在远处仍会缩到格子以下 ⇒ ⛔ 单点双线性会采出锯齿,mipmap 照旧开
61
+ * (WebGL2 对 NPOT 也能生成 mipmap,three r163 起只支持 WebGL2)。
54
62
  * ② 槽 RT 标 `generateMipmaps`:three 每次渲染进 RT 后自己 `glGenerateMipmap`(`WebGLRenderer.js`
55
63
  * `updateRenderTargetMipmap`)⇒ 与当年 CanvasTexture 上传后生成 mipmap 同一口径。
56
64
  * ⚠️ 代价:每格一次(旧版每格也是一次),都在 GPU 上、不占主线程。
57
- * 4. **接缝**:槽内 4×4 = 16 格 ⇒ 一张槽内部的格子线从 1 横 1 竖变成 3 横 3 竖。治法 = 上传时每片向四边**多切**
65
+ * 4. **接缝**:槽内「横 1 × 竖 3」⇒ 一张槽内部的格子线是 **2 横 0 竖**(上一版 4096² 是 3 横 3 竖);
66
+ * 槽与槽之间另有 u=1/3、u=2/3 两道**跨纹理**竖缝,那道治不了(见 `PanoTexMode` 说明②)。治法 = 上传时每片向四边**多切**
58
67
  * `RT_STITCH_SEAM_MARGIN_PX` 像素、拼合时**只采中间那块**(`uSrcRect`)⇒ 边界插值拿得到真邻居像素,
59
68
  * 不再复制自己的边缘。⛔ 这条不许「顺手简化」成直接铺满格子:几何会挤歪,缝也还在。
60
69
  * 详见 `./mktShader` 里那三个 `rtStitch*` 纯函数 —— **上传侧与拼合侧必须共用同一份几何**。
61
70
  *
62
- * 资源账(⚠️ 如实记;10-06 晚改 8×4=32 片 / 左右 2 槽后重算):
63
- * - 常驻显存:2 槽 × (4096×**4096**×4B + mipmap≈33%) ≈ **170MB/全景** —— 与 16 片、64 片时代**一字不变**
64
- * (合计像素相同,只是从「4 张 4096×2048」变成「2 张 4096×4096」)。
65
- * ⚠️ 但同屏从 4 个全景变 **8 个** ⇒ 同屏 RT 显存 683MB → **1.33GB**;13 个全景全常驻 ≈ 2.2GB
66
- * (上一轮已奉旨取消 LRU 淘汰 ⇒ 这些真会常驻,皇上已知情)。
67
- * - 上传总量:32 片 × ~9.2MB ≈ 292MB/全景 ⇒ 总量不变、**片数减半**(64 片那版每片 4.6MB × 64)。
68
- * - 瓦片纹理画完即弃 ⇒ 同时在册的只有「下载中 + 已解码待上传」那些,上限 8+8 片 ≈ 73MB CPU/拼合器。
71
+ * 资源账(⚠️ 如实记;10-23 旨改 3×3=9 片 / 按列 3 槽 / 4096×6144 后重算):
72
+ * - 常驻显存:3 槽 × (4096×**6144**×4B + mipmap≈33%) ≈ **384MB/全景**(不含 mipmap 288MB)
73
+ * —— 上一版 2 槽 4096² 是 **170MB/全景** ⇒ 单全景变成 **2.26 倍**,代价来自「槽与原图一比一」那次**升分辨率**,
74
+ * 不是浪费:33.5Mpx → 75.5Mpx 的有效像素全在 RT 里。
75
+ * ⚠️ 组**运行中不淘汰**(见文末那条旨)⇒ 13 张全景全常驻 ≈ **4.9GB**(上一版 2.2GB)。同屏景点数虽收到 5,
76
+ * 那只省 sampler 绑定、**不省显存**。⚠️ 这是本轮最大的风险项,已另记待皇上裁决(见「RT 不回收」)。
77
+ * - 上传总量:同一张原图切完 ⇒ **总像素与 32 片那版逐字节同量**,只是片数 32 → 9、单片边长 ~2.7 倍
78
+ * ⇒ 下载发起数减 72%、单请求变大。
79
+ * - 瓦片纹理画完即弃 ⇒ 同时在册的只有「下载中 + 已解码待上传」那些,上限 8+8 片;
80
+ * ⚠️ 每片解码位图 4096×2048×4B = **32MB**(上一版每片 8.5MB)⇒ 峰值 CPU 侧 ≈ **512MB**(原 ~136MB)。
69
81
  *
70
82
  * ⚠️ RT 是 GL 上下文私有资源:`pump(renderer)` 换 renderer 时整组作废(`_switchRenderer`),下一帧消费方
71
83
  * 照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存重新下载)。现况是**每个持有者一份拼合器 + 一个 renderer**
@@ -78,7 +90,7 @@ import { ClampToEdgeWrapping, DataTexture, LinearFilter, LinearMipmapLinearFilte
78
90
  import { loadPanoTexture, releasePanoTexture } from "./panoBitmapTexture";
79
91
  // ⭐ 2026-10-09 皇上旨「上传也走 threeUtils.addWork 队列」:上传节流改为挂公共队列,见 `_uploadPass`。
80
92
  import threeUtils from "../threeUtils";
81
- import { MISSING_TILE_GRAY, RT_STITCH_CELLS_PER_SLOT, RT_STITCH_SLOT_COUNT, RT_STITCH_SLOT_H, RT_STITCH_SLOT_TILES, RT_STITCH_SLOT_W, RT_STITCH_SLOT_GRID, RT_STITCH_TILES_PER_BATCH, rtStitchBatchOf, rtStitchCellPos, rtStitchTileInset, } from "./mktShader";
93
+ import { MISSING_TILE_GRAY, rtStitchBatchOf, rtStitchCellPos, rtStitchTileInset, PANO_TILE_LAYOUT, cellsPerSlotOf, tilesPerBatchOf, slotTilesOf, } from "./mktShader";
82
94
  /**
83
95
  * ⛔⛔ 2026-10-09 皇上旨:「每帧最多几张新纹理进 GPU」这条**节流已不再由此常量实现**,改由
84
96
  * `threeUtils.addWork` 队列承担(见 `_uploadPass`)—— 因为函数内局部 budget 管不住「一帧多次 pump」。
@@ -86,7 +98,7 @@ import { MISSING_TILE_GRAY, RT_STITCH_CELLS_PER_SLOT, RT_STITCH_SLOT_COUNT, RT_S
86
98
  * ⛔ 别再拿它去写 budget 循环:那正是「一帧上传 2 张」的根因(消费方 onBeforeRender 里 pump 了两次)。
87
99
  */
88
100
  const PANO_STITCH_UPLOADS_PER_FRAME = 1;
89
- /** 每帧最多发起几张下载(32 片一批,⛔ 不加以限制就是一帧甩 32 条 XHR + 32 次解码) */
101
+ /** 每帧最多发起几张下载(10-23 起一批 9 片,⛔ 不加以限制就是一帧甩 9 条 XHR + 9 次 800 万像素解码) */
90
102
  const PANO_STITCH_DOWNLOADS_PER_FRAME = 2;
91
103
  /** 「下载中」的片数上限(跨所有批次) */
92
104
  const PANO_STITCH_MAX_INFLIGHT = 8;
@@ -174,7 +186,8 @@ export class PanoTileStitcher {
174
186
  depthWrite: false,
175
187
  blending: NoBlending,
176
188
  });
177
- /** 几何是 2×2(铺满正交相机的 [-1,1]²),每格只把它缩到 1/4 再挪过去 ⇒ 一格 = 槽的 1/16 */
189
+ /** 几何是 2×2(铺满正交相机的 [-1,1]²),每格按 `1/slotCellCols × 1/slotCellRows` 缩放再挪过去
190
+ * ⇒ 全景一格 = 槽的 1/3(横 1 竖 3,10-23 旨),图片平面一格 = 1/4(横 2 竖 2) */
178
191
  _quad = new Mesh(new PlaneGeometry(2, 2), this._mat);
179
192
  /** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
180
193
  _blank = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
@@ -186,30 +199,38 @@ export class PanoTileStitcher {
186
199
  this._blank.needsUpdate = true;
187
200
  }
188
201
  /** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
189
- * ⭐ 09-29 晚旨「不足的涂灰」+ 10-06 晚旨「8×4 = 32 片」:1~32 条皆可入组,内部补空串占位到 32;
202
+ * ⭐ 09-29 晚旨「不足的涂灰」+ 10-23 旨「3×3 = 9 片」:1~9 条皆可入组,内部补空串占位到 9;
190
203
  * 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
191
- * ⛔ 旧版 16 条(4×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
192
- * 64 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
193
- * ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null,null]` —— 槽数再变会漏改)。 */
194
- ensure(urls) {
195
- if (!urls || urls.length === 0 || urls.length > RT_STITCH_TILES_PER_BATCH)
204
+ * ⛔ 旧版 16 条(4×4)、32 条(8×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
205
+ * 超 9 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
206
+ * ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null]` —— 槽数再变会漏改)。 */
207
+ ensure(urls, layout) {
208
+ const L = layout ?? PANO_TILE_LAYOUT;
209
+ const maxTiles = tilesPerBatchOf(L);
210
+ if (!urls || urls.length === 0 || urls.length > maxTiles)
196
211
  return;
197
212
  const u = urls.slice();
198
- while (u.length < RT_STITCH_TILES_PER_BATCH)
213
+ while (u.length < maxTiles)
199
214
  u.push("");
200
215
  const batch = rtStitchBatchOf(u);
201
216
  if (this._groups.has(batch))
202
- return; // ⭐ 已建组:什么都不做(绝不重建 ⇒ 永不二次解码)
217
+ return;
218
+ const slotCount = L.slotCount;
219
+ const cellsPerSlot = cellsPerSlotOf(L);
203
220
  this._groups.set(batch, {
221
+ layout: L,
222
+ slotTiles: slotTilesOf(L),
223
+ tileCount: maxTiles,
224
+ cellsPerSlot,
204
225
  urls: u,
205
- rts: Array.from({ length: RT_STITCH_SLOT_COUNT }, () => null),
206
- drawn: Array.from({ length: RT_STITCH_SLOT_COUNT }, () => 0),
207
- drawnCount: Array.from({ length: RT_STITCH_SLOT_COUNT }, () => 0),
208
- pending: RT_STITCH_SLOT_COUNT,
226
+ rts: Array.from({ length: slotCount }, () => null),
227
+ drawn: Array.from({ length: slotCount }, () => 0),
228
+ drawnCount: Array.from({ length: slotCount }, () => 0),
229
+ pending: slotCount,
209
230
  });
210
231
  this._stamp++;
211
232
  }
212
- /** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 16 格画满)。
233
+ /** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
213
234
  * RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
214
235
  peek(batch, slot, renderer) {
215
236
  const g = this._groups.get(batch);
@@ -303,16 +324,16 @@ export class PanoTileStitcher {
303
324
  for (const g of this._groups.values()) {
304
325
  if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
305
326
  return;
306
- for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
307
- const ids = RT_STITCH_SLOT_TILES[slot];
308
- for (let cell = 0; cell < RT_STITCH_CELLS_PER_SLOT; cell++) {
327
+ for (let slot = 0; slot < g.layout.slotCount; slot++) {
328
+ const ids = g.slotTiles[slot];
329
+ for (let cell = 0; cell < g.cellsPerSlot; cell++) {
309
330
  if (g.drawn[slot] & (1 << cell))
310
- continue; // 画过的格子那片早释放了,不用再来
331
+ continue;
311
332
  const url = g.urls[ids[cell]];
312
333
  if (!url || this._dead.has(url))
313
- continue; // 缺格 / 死链:涂灰那一路,不需要纹理
334
+ continue;
314
335
  if (this._texs.has(url))
315
- continue; // 已在册(下载中或已解码)⇒ ⛔ 发第二遍
336
+ continue;
316
337
  this._loadTile(url);
317
338
  inflight++;
318
339
  if (--budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
@@ -328,23 +349,23 @@ export class PanoTileStitcher {
328
349
  for (const g of this._groups.values()) {
329
350
  if (g.pending <= 0)
330
351
  continue;
331
- for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
332
- if (g.drawnCount[slot] >= RT_STITCH_CELLS_PER_SLOT)
352
+ for (let slot = 0; slot < g.layout.slotCount; slot++) {
353
+ if (g.drawnCount[slot] >= g.cellsPerSlot)
333
354
  continue;
334
- const ids = RT_STITCH_SLOT_TILES[slot];
335
- for (let cell = 0; cell < RT_STITCH_CELLS_PER_SLOT; cell++) {
355
+ const ids = g.slotTiles[slot];
356
+ for (let cell = 0; cell < g.cellsPerSlot; cell++) {
336
357
  if (g.drawn[slot] & (1 << cell))
337
358
  continue;
338
359
  const url = g.urls[ids[cell]];
339
360
  if (!url || this._dead.has(url)) {
340
- this._stitchCell(renderer, g, slot, cell, null); // 缺格:涂灰,不占任何预算
361
+ this._stitchCell(renderer, g, slot, cell, null);
341
362
  continue;
342
363
  }
343
364
  const rec = this._texs.get(url);
344
365
  if (!rec || !rec.tex || !rec.uploaded)
345
- continue; // 还没上 GPU:等下一帧(工序 3 会排它)
366
+ continue;
346
367
  this._stitchCell(renderer, g, slot, cell, rec.tex);
347
- this._dropTile(url); // 一格只用这一片 ⇒ 画完即弃(显存峰值 = 一格)
368
+ this._dropTile(url);
348
369
  }
349
370
  }
350
371
  }
@@ -367,18 +388,14 @@ export class PanoTileStitcher {
367
388
  * 与 `_drawPass` 天然错开(上传与绘制不落同一拍,正是 09-29「两个大头别在同一帧」的延续)。
368
389
  */
369
390
  _uploadPass(renderer) {
370
- // ⛔⛔ 队列里已有待上传项 ⇒ **本次直接不排**。
371
- // 这一句是本改法的关键闸门:若只靠下面 `has(rec)` 跳过「同一片」,遍历会**继续往下找下一片**
372
- // 于是 pump 几次就把多张全排进队列(13 全景 × 32 片 = 416 项 ⇒ workList 膨胀、`includes` 退化成 O(n)、
373
- // 队尾那张要等几百帧才传完)。先判「队列非空就收手」⇒ 队列恒 ≤2 项(一占位+一真活),延迟最低。
374
391
  if (this._uploadQueued.size > 0)
375
392
  return;
376
393
  for (const g of this._groups.values()) {
377
394
  if (g.pending <= 0)
378
395
  continue;
379
- for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
380
- const ids = RT_STITCH_SLOT_TILES[slot];
381
- for (let cell = 0; cell < RT_STITCH_CELLS_PER_SLOT; cell++) {
396
+ for (let slot = 0; slot < g.layout.slotCount; slot++) {
397
+ const ids = g.slotTiles[slot];
398
+ for (let cell = 0; cell < g.cellsPerSlot; cell++) {
382
399
  if (g.drawn[slot] & (1 << cell))
383
400
  continue;
384
401
  const url = g.urls[ids[cell]];
@@ -387,13 +404,12 @@ export class PanoTileStitcher {
387
404
  const rec = this._texs.get(url);
388
405
  if (!rec || !rec.tex || rec.uploaded)
389
406
  continue;
390
- // ⛔ 已在队列里 ⇒ 跳过(`addWork` 按引用判重对「新闭包」无效 ⇒ 必须自备 Set,见 `_uploadQueued`)
391
407
  if (this._uploadQueued.has(rec))
392
408
  continue;
393
409
  this._uploadQueued.add(rec);
394
- threeUtils.addWork(() => { }); // 占一格(什么都不干),把真活推到下一个 tick
410
+ threeUtils.addWork(() => { });
395
411
  threeUtils.addWork(() => this._uploadTile(rec));
396
- return; // ⛔ 本次 pump 只排一张:其余名额留给下一帧的队列,绝不在这一帧再排第二张
412
+ return;
397
413
  }
398
414
  }
399
415
  }
@@ -412,27 +428,30 @@ export class PanoTileStitcher {
412
428
  try {
413
429
  renderer.initTexture(rec.tex);
414
430
  }
415
- catch (e) {
416
- console.warn("[PanoTileStitcher] 瓦片预上传失败,交给绘制那一帧自己上传:", e);
431
+ catch {
432
+ // 皇上旨 2026-10-12「日志都去掉」⇒ 不再出声;下面照记 `uploaded = true`,
433
+ // 最坏退回绘制那一帧自己上传。
417
434
  }
418
435
  rec.uploaded = true;
419
436
  this._stamp++;
420
437
  }
421
438
  // ───────────────────────── 内部:单格绘制 ─────────────────────────
422
- /** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(16 格里的一格)。
423
- * ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 16 帧**,
439
+ /** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(每槽 `slotCellCols × slotCellRows` 格里的一格,
440
+ * 10-23 旨后全景是「横 1 × 竖 3」= 3 格、图片平面是 2×2 = 4 格)。
441
+ * ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 cellsPerSlot 帧**,
424
442
  * 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
425
443
  _stitchCell(renderer, g, slot, cell, tex) {
426
444
  const rt = this._ensureRt(renderer, g, slot);
427
- const tileIdx = RT_STITCH_SLOT_TILES[slot][cell];
428
- const [cc, rr] = rtStitchCellPos(cell);
429
- // 正交相机铺满 [-1,1]²:quad 缩到 1/4 ⇒ 占一格;`rtStitchCellPos` 是**图像方向**的 [列, 行],
430
- // GL 的 y 向上 ⇒ 图像第一行落在 RT 的上半(v=1),故 y 取反。
431
- this._quad.scale.set(1 / RT_STITCH_SLOT_GRID, 1 / RT_STITCH_SLOT_GRID, 1);
432
- this._quad.position.set((2 * cc + 1) / RT_STITCH_SLOT_GRID - 1, 1 - (2 * rr + 1) / RT_STITCH_SLOT_GRID, 0);
445
+ const tileIdx = g.slotTiles[slot][cell];
446
+ const [cc, rr] = rtStitchCellPos(cell, g.layout.slotCellCols);
447
+ // ⚠️ 横竖**分轴**缩放/摆位(旧的单一 `slotGrid` 方形假设整条作废):10-23 旨一槽 = 原图一整列 ⇒ 1 横 3 竖
448
+ const cw = g.layout.slotCellCols;
449
+ const ch = g.layout.slotCellRows;
450
+ this._quad.scale.set(1 / cw, 1 / ch, 1);
451
+ this._quad.position.set((2 * cc + 1) / cw - 1, 1 - (2 * rr + 1) / ch, 0);
433
452
  this._mat.uniforms.uMap.value = tex ?? this._blank;
434
453
  this._mat.uniforms.uMissing.value = tex ? 0 : 1;
435
- const rect = tex ? rtStitchTileInset(tileIdx, texW(tex), texH(tex)) : null;
454
+ const rect = tex ? rtStitchTileInset(tileIdx, texW(tex), texH(tex), g.layout) : null;
436
455
  const uv = this._mat.uniforms.uSrcRect.value;
437
456
  if (rect)
438
457
  uv.set(rect[0], rect[1], rect[2], rect[3]);
@@ -442,7 +461,7 @@ export class PanoTileStitcher {
442
461
  g.drawn[slot] |= 1 << cell;
443
462
  g.drawnCount[slot]++;
444
463
  this._stamp++;
445
- if (g.drawnCount[slot] >= RT_STITCH_CELLS_PER_SLOT)
464
+ if (g.drawnCount[slot] >= g.cellsPerSlot)
446
465
  g.pending--;
447
466
  if (g.pending <= 0)
448
467
  this._releaseGroupWanted(g);
@@ -539,13 +558,15 @@ export class PanoTileStitcher {
539
558
  this._fails.clear();
540
559
  this._dead.clear();
541
560
  }
542
- /** 懒建某槽的渲染目标:4096×4096 RGBA8 + sRGB + mipmap,并**立刻整张涂缺失灰**
543
- * (⛔ 未定义像素不许进 shader:`peek()` 现在画过 1 格就交出去)。 */
561
+ /** 懒建某槽的渲染目标:尺寸取自 `g.layout.rtW/rtH`(全景档 = **4096×6144**,10-23 旨)、RGBA8 + sRGB + mipmap,
562
+ * 并**立刻整张涂缺失灰**(⛔ 未定义像素不许进 shader:`peek()` 现在画过 1 格就交出去)。
563
+ * ⚠️ 6144 这个数受本机 `MAX_TEXTURE_SIZE` / 最大渲染目标尺寸约束(ANGLE/D3D11 的 11_0 是 16384,远超;
564
+ * 只有老 Intel 集显才可能卡 4096 —— 真建不出来 three 会报 `GL_INVALID_VALUE`,届时墙上整片缺失灰)。 */
544
565
  _ensureRt(renderer, g, slot) {
545
566
  const exist = g.rts[slot];
546
567
  if (exist)
547
568
  return exist;
548
- const rt = new WebGLRenderTarget(RT_STITCH_SLOT_W, RT_STITCH_SLOT_H, {
569
+ const rt = new WebGLRenderTarget(g.layout.rtW, g.layout.rtH, {
549
570
  wrapS: ClampToEdgeWrapping,
550
571
  wrapT: ClampToEdgeWrapping,
551
572
  minFilter: LinearMipmapLinearFilter,
@@ -580,7 +601,7 @@ export class PanoTileStitcher {
580
601
  }
581
602
  /** 释放一组的全部 GPU 资源:2 张 RT + 还没画掉的瓦片纹理 */
582
603
  _dropGroupGpu(g) {
583
- for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
604
+ for (let slot = 0; slot < g.layout.slotCount; slot++) {
584
605
  const rt = g.rts[slot];
585
606
  if (rt)
586
607
  rt.dispose();