three-base 2.0.134 → 2.0.142

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 (305) hide show
  1. package/DirectionalLight.d.ts +6 -6
  2. package/DirectionalLight.js +68 -68
  3. package/DirectionalLight.ts +70 -0
  4. package/EventMap.d.ts +103 -0
  5. package/EventMap.js +17 -0
  6. package/EventMap.ts +139 -0
  7. package/EventMesh.d.ts +18 -18
  8. package/EventMesh.js +59 -59
  9. package/EventMesh.ts +60 -0
  10. package/ExtrudeMesh.d.ts +112 -112
  11. package/ExtrudeMesh.js +342 -366
  12. package/ExtrudeMesh.ts +553 -0
  13. package/FPScontrol.d.ts +511 -71
  14. package/FPScontrol.js +1799 -505
  15. package/FPScontrol.ts +1855 -0
  16. package/Fire/Fire.d.ts +28 -28
  17. package/Fire/Fire.js +80 -79
  18. package/Fire/Fire.ts +89 -0
  19. package/Fire/{FireGromtry.d.ts → FireGeometry.d.ts} +9 -9
  20. package/Fire/{FireGromtry.js → FireGeometry.js} +40 -40
  21. package/Fire/FireGeometry.ts +40 -0
  22. package/Fire/FireMaterial.d.ts +28 -28
  23. package/Fire/FireMaterial.js +32 -32
  24. package/Fire/FireMaterial.ts +89 -0
  25. package/Fire/index.d.ts +2 -1
  26. package/Fire/index.js +2 -1
  27. package/Fire/index.ts +2 -0
  28. package/GLTFDracoLoader.d.ts +71 -0
  29. package/GLTFDracoLoader.js +266 -0
  30. package/GLTFDracoLoader.ts +430 -0
  31. package/Grass/Grass.d.ts +8 -8
  32. package/Grass/Grass.js +38 -38
  33. package/Grass/Grass.ts +38 -0
  34. package/Grass/index.d.ts +2 -1
  35. package/Grass/index.js +2 -1
  36. package/Grass/index.ts +2 -0
  37. package/Ground1/Ground1.d.ts +10 -11
  38. package/Ground1/Ground1.js +95 -91
  39. package/Ground1/Ground1.ts +114 -0
  40. package/Ground1/index.d.ts +1 -1
  41. package/Ground1/index.js +1 -1
  42. package/Ground1/index.ts +1 -0
  43. package/MeshBase.d.ts +36 -35
  44. package/MeshBase.js +247 -223
  45. package/MeshBase.ts +253 -0
  46. package/Mirror.d.ts +25 -25
  47. package/Mirror.js +143 -139
  48. package/Mirror.ts +203 -0
  49. package/MktMesh/MktMesh.d.ts +291 -0
  50. package/MktMesh/MktMesh.js +1045 -0
  51. package/MktMesh/MktMesh.ts +1075 -0
  52. package/MktMesh/assetUrl.d.ts +35 -0
  53. package/MktMesh/assetUrl.js +69 -0
  54. package/MktMesh/assetUrl.ts +69 -0
  55. package/MktMesh/mktMappingShader.d.ts +12 -0
  56. package/MktMesh/mktMappingShader.js +139 -0
  57. package/MktMesh/mktMappingShader.ts +139 -0
  58. package/MktMesh/mktShader.d.ts +263 -0
  59. package/MktMesh/mktShader.js +1248 -0
  60. package/MktMesh/mktShader.ts +1344 -0
  61. package/MktMesh/panoBitmapTexture.d.ts +25 -0
  62. package/MktMesh/panoBitmapTexture.js +52 -0
  63. package/MktMesh/panoBitmapTexture.ts +54 -0
  64. package/MktMesh/panoSlotBind.d.ts +29 -0
  65. package/MktMesh/panoSlotBind.js +106 -0
  66. package/MktMesh/panoSlotBind.ts +153 -0
  67. package/MktMesh/panoSlotRoster.d.ts +22 -0
  68. package/MktMesh/panoSlotRoster.js +169 -0
  69. package/MktMesh/panoSlotRoster.ts +168 -0
  70. package/MktMesh/panoTileStitcher.d.ts +119 -0
  71. package/MktMesh/panoTileStitcher.js +533 -0
  72. package/MktMesh/panoTileStitcher.ts +616 -0
  73. package/MktMesh/types.d.ts +75 -0
  74. package/MktMesh/types.js +1 -0
  75. package/MktMesh/types.ts +87 -0
  76. package/MktMesh/wallCapTessellate.d.ts +33 -0
  77. package/MktMesh/wallCapTessellate.js +97 -0
  78. package/MktMesh/wallCapTessellate.ts +68 -0
  79. package/PipeMesh/PathGeometry.d.ts +44 -44
  80. package/PipeMesh/PathGeometry.js +307 -300
  81. package/PipeMesh/PathGeometry.ts +343 -0
  82. package/PipeMesh/PathPoint.d.ts +17 -17
  83. package/PipeMesh/PathPoint.js +31 -31
  84. package/PipeMesh/PathPoint.ts +36 -0
  85. package/PipeMesh/PathPointList.d.ts +31 -31
  86. package/PipeMesh/PathPointList.js +209 -208
  87. package/PipeMesh/PathPointList.ts +227 -0
  88. package/PipeMesh/PathTubeGeometry.d.ts +25 -25
  89. package/PipeMesh/PathTubeGeometry.js +130 -130
  90. package/PipeMesh/PathTubeGeometry.ts +146 -0
  91. package/PipeMesh/PipeMesh.d.ts +25 -25
  92. package/PipeMesh/PipeMesh.js +63 -63
  93. package/PipeMesh/PipeMesh.ts +76 -0
  94. package/PipeMesh/index.d.ts +1 -1
  95. package/PipeMesh/index.js +1 -1
  96. package/PipeMesh/index.ts +1 -0
  97. package/PlaneModal1/PlaneModal1.d.ts +34 -31
  98. package/PlaneModal1/PlaneModal1.js +109 -105
  99. package/PlaneModal1/PlaneModal1.ts +138 -0
  100. package/PlaneModal1/index.d.ts +1 -1
  101. package/PlaneModal1/index.js +1 -1
  102. package/PlaneModal1/index.ts +1 -0
  103. package/PlaneText.d.ts +27 -39
  104. package/PlaneText.js +35 -30
  105. package/PlaneText.ts +55 -0
  106. package/PlaneWithHtml.d.ts +13 -11
  107. package/PlaneWithHtml.js +64 -63
  108. package/PlaneWithHtml.ts +70 -0
  109. package/PointLight/PointLight.d.ts +13 -14
  110. package/PointLight/PointLight.js +64 -70
  111. package/PointLight/PointLight.ts +67 -0
  112. package/PointLight/index.d.ts +1 -1
  113. package/PointLight/index.js +1 -1
  114. package/PointLight/index.ts +1 -0
  115. package/README.md +110 -79
  116. package/Sky/Sky.d.ts +9 -9
  117. package/Sky/Sky.js +22 -22
  118. package/Sky/Sky.ts +22 -0
  119. package/Sky/index.d.ts +1 -1
  120. package/Sky/index.js +1 -1
  121. package/Sky/index.ts +1 -0
  122. package/SpotLight.d.ts +6 -6
  123. package/SpotLight.js +72 -72
  124. package/SpotLight.ts +74 -0
  125. package/SpriteModal1/SpriteModal1.d.ts +33 -30
  126. package/SpriteModal1/SpriteModal1.js +116 -110
  127. package/SpriteModal1/SpriteModal1.ts +146 -0
  128. package/SpriteModal1/index.d.ts +1 -1
  129. package/SpriteModal1/index.js +1 -1
  130. package/SpriteModal1/index.ts +1 -0
  131. package/SpriteText.d.ts +46 -45
  132. package/SpriteText.js +176 -163
  133. package/SpriteText.ts +205 -0
  134. package/SpriteText2.d.ts +48 -47
  135. package/SpriteText2.js +223 -198
  136. package/SpriteText2.ts +258 -0
  137. package/SpriteText3.d.ts +86 -0
  138. package/SpriteText3.js +519 -0
  139. package/SpriteText3.ts +599 -0
  140. package/SpriteWithHtml.d.ts +9 -7
  141. package/SpriteWithHtml.js +44 -44
  142. package/SpriteWithHtml.ts +50 -0
  143. package/Text3D/Text3D.d.ts +20 -20
  144. package/Text3D/Text3D.js +58 -62
  145. package/Text3D/Text3D.ts +77 -0
  146. package/Text3D/fontController.d.ts +60 -59
  147. package/Text3D/fontController.js +166 -199
  148. package/Text3D/fontController.ts +209 -0
  149. package/Text3D/index.d.ts +1 -1
  150. package/Text3D/index.js +1 -1
  151. package/Text3D/index.ts +1 -0
  152. package/ThreeBase.d.ts +248 -97
  153. package/ThreeBase.js +1202 -613
  154. package/ThreeBase.ts +1314 -0
  155. package/ThreeMouse.d.ts +54 -40
  156. package/ThreeMouse.js +453 -407
  157. package/ThreeMouse.ts +472 -0
  158. package/Transition.d.ts +45 -40
  159. package/Transition.js +209 -210
  160. package/Transition.ts +242 -0
  161. package/Water/Water.d.ts +5 -5
  162. package/Water/Water.js +44 -26
  163. package/Water/Water.ts +45 -0
  164. package/Water/index.d.ts +2 -0
  165. package/Water/index.js +2 -0
  166. package/Water/index.ts +2 -0
  167. package/WaterFlow/FloatingDuck.d.ts +88 -0
  168. package/WaterFlow/FloatingDuck.js +307 -0
  169. package/WaterFlow/FloatingDuck.ts +445 -0
  170. package/WaterFlow/GPUFluidSimulation.d.ts +56 -0
  171. package/WaterFlow/GPUFluidSimulation.js +785 -0
  172. package/WaterFlow/GPUFluidSimulation.ts +827 -0
  173. package/WaterFlow/ReservoirSystem.d.ts +106 -0
  174. package/WaterFlow/ReservoirSystem.js +621 -0
  175. package/WaterFlow/ReservoirSystem.ts +754 -0
  176. package/WaterFlow/SPHField.d.ts +35 -0
  177. package/WaterFlow/SPHField.js +204 -0
  178. package/WaterFlow/SPHField.ts +228 -0
  179. package/WaterFlow/ScreenSpaceFluidPass.d.ts +69 -0
  180. package/WaterFlow/ScreenSpaceFluidPass.js +736 -0
  181. package/WaterFlow/ScreenSpaceFluidPass.ts +789 -0
  182. package/WaterFlow/ShallowWaterSurface.d.ts +45 -0
  183. package/WaterFlow/ShallowWaterSurface.js +441 -0
  184. package/WaterFlow/ShallowWaterSurface.ts +510 -0
  185. package/WaterFlow/WaterFlow.d.ts +87 -0
  186. package/WaterFlow/WaterFlow.js +171 -0
  187. package/WaterFlow/WaterFlow.ts +219 -0
  188. package/WaterFlow/types.d.ts +161 -0
  189. package/WaterFlow/types.js +1 -0
  190. package/WaterFlow/types.ts +189 -0
  191. package/WaterFlow/waterFlowPass.d.ts +3 -0
  192. package/WaterFlow/waterFlowPass.js +20 -0
  193. package/WaterFlow/waterFlowPass.ts +40 -0
  194. package/collisionOBB.d.ts +42 -0
  195. package/collisionOBB.js +112 -0
  196. package/collisionOBB.ts +108 -0
  197. package/declare.d.ts +23 -165
  198. package/index.d.ts +1 -1
  199. package/index.js +1 -2
  200. package/index.ts +1 -0
  201. package/instanced/InstancedBatch.d.ts +57 -0
  202. package/instanced/InstancedBatch.js +348 -0
  203. package/instanced/InstancedBatch.ts +406 -0
  204. package/instanced/InstancedChild.d.ts +174 -0
  205. package/instanced/InstancedChild.js +264 -0
  206. package/instanced/InstancedChild.ts +327 -0
  207. package/instanced/InstancedGroup.d.ts +43 -0
  208. package/instanced/InstancedGroup.js +126 -0
  209. package/instanced/InstancedGroup.ts +137 -0
  210. package/{InstancedMeshBase.d.ts → instanced/InstancedMeshBase.d.ts} +38 -39
  211. package/{InstancedMeshBase.js → instanced/InstancedMeshBase.js} +282 -293
  212. package/instanced/InstancedMeshBase.ts +292 -0
  213. package/instanced/InstancedTemplate.d.ts +46 -0
  214. package/instanced/InstancedTemplate.js +125 -0
  215. package/instanced/InstancedTemplate.ts +188 -0
  216. package/keyControls.d.ts +30 -0
  217. package/{keyContorls.js → keyControls.js} +101 -98
  218. package/keyControls.ts +107 -0
  219. package/lensflare/LensFlare.d.ts +39 -0
  220. package/lensflare/LensFlare.js +373 -0
  221. package/lensflare/LensFlare.ts +448 -0
  222. package/lensflare/lensflare0.png +0 -0
  223. package/lensflare/lensflare3.png +0 -0
  224. package/lensflare/lensflare_custom.png +0 -0
  225. package/lensflare/lensflare_hex_ghost.png +0 -0
  226. package/lensflare/lensflare_hex_main.png +0 -0
  227. package/package.json +101 -26
  228. package/pass/bloom_pass.d.ts +2 -2
  229. package/pass/bloom_pass.js +188 -101
  230. package/pass/bloom_pass.ts +243 -0
  231. package/pass/cover_pass.d.ts +2 -2
  232. package/pass/cover_pass.js +122 -99
  233. package/pass/cover_pass.ts +182 -0
  234. package/pass/debugDepthState.d.ts +20 -0
  235. package/pass/debugDepthState.js +9 -0
  236. package/pass/debugDepthState.ts +22 -0
  237. package/pass/depthTestBias.d.ts +12 -0
  238. package/pass/depthTestBias.js +12 -0
  239. package/pass/depthTestBias.ts +12 -0
  240. package/pass/effectUserData.d.ts +27 -0
  241. package/pass/effectUserData.js +1 -0
  242. package/pass/effectUserData.ts +46 -0
  243. package/pass/frosted_glass_pass.d.ts +2 -2
  244. package/pass/frosted_glass_pass.js +167 -186
  245. package/pass/frosted_glass_pass.ts +288 -0
  246. package/pass/fsQuad.d.ts +9 -9
  247. package/pass/fsQuad.js +28 -28
  248. package/pass/fsQuad.ts +34 -0
  249. package/pass/glow_pass.d.ts +2 -2
  250. package/pass/glow_pass.js +449 -233
  251. package/pass/glow_pass.ts +654 -0
  252. package/pass/screen_bloom_pass.d.ts +11 -0
  253. package/pass/screen_bloom_pass.js +235 -0
  254. package/pass/screen_bloom_pass.ts +263 -0
  255. package/pass/ssao_pass.d.ts +88 -11
  256. package/pass/ssao_pass.js +802 -288
  257. package/pass/ssao_pass.ts +918 -0
  258. package/pass/stroke_pass.d.ts +2 -2
  259. package/pass/stroke_pass.js +329 -258
  260. package/pass/stroke_pass.ts +638 -0
  261. package/shaders/FXAAShaderMaterial.d.ts +3 -3
  262. package/shaders/FXAAShaderMaterial.js +23 -66
  263. package/shaders/FXAAShaderMaterial.ts +201 -0
  264. package/shaders/baseDepthVisShader.d.ts +11 -0
  265. package/shaders/baseDepthVisShader.js +36 -0
  266. package/shaders/baseDepthVisShader.ts +36 -0
  267. package/shaders/baseTextureToRenderShader.d.ts +2 -2
  268. package/shaders/baseTextureToRenderShader.js +55 -60
  269. package/shaders/baseTextureToRenderShader.ts +232 -0
  270. package/shaders/copyShader.d.ts +3 -3
  271. package/shaders/copyShader.js +13 -14
  272. package/shaders/copyShader.ts +32 -0
  273. package/shaders/debugDepthCompareShader.d.ts +14 -0
  274. package/shaders/debugDepthCompareShader.js +49 -0
  275. package/shaders/debugDepthCompareShader.ts +50 -0
  276. package/shaders/debugDepthJudgeShader.d.ts +18 -0
  277. package/shaders/debugDepthJudgeShader.js +52 -0
  278. package/shaders/debugDepthJudgeShader.ts +54 -0
  279. package/shaders/debugExcludedShader.d.ts +13 -0
  280. package/shaders/debugExcludedShader.js +46 -0
  281. package/shaders/debugExcludedShader.ts +48 -0
  282. package/spriteTextBg.d.ts +1 -0
  283. package/spriteTextBg.js +20 -0
  284. package/spriteTextBg.ts +20 -0
  285. package/threeLoader.d.ts +35 -34
  286. package/threeLoader.js +518 -527
  287. package/threeLoader.ts +525 -0
  288. package/threeUtils.d.ts +53 -52
  289. package/threeUtils.js +329 -330
  290. package/threeUtils.ts +373 -0
  291. package/types.d.ts +2 -0
  292. package/types.js +6 -0
  293. package/types.ts +8 -0
  294. package/ApngTexture.d.ts +0 -13
  295. package/ApngTexture.js +0 -61
  296. package/ApngTextureManage.d.ts +0 -18
  297. package/ApngTextureManage.js +0 -87
  298. package/KeyFrameAnimation.d.ts +0 -25
  299. package/KeyFrameAnimation.js +0 -78
  300. package/keyContorls.d.ts +0 -25
  301. package/lib/draco/draco_decoder.js +0 -34
  302. package/lib/draco/draco_decoder.wasm +0 -0
  303. package/lib/draco/draco_wasm_wrapper.js +0 -117
  304. package/shaders.d.ts +0 -5
  305. package/shaders.js +0 -93
@@ -0,0 +1,616 @@
1
+ /*
2
+ * @Description: 32 瓦片全景的「离屏拼合器」= **shader 逐格画进 WebGLRenderTarget**(本项目唯一一份拼合实现)。
3
+ *
4
+ * 三道旨意叠成现在这一份:
5
+ * ① 2026-09-29:一张全景切多片(每片所有 GPU 都装得下),运行时离屏拼回若干张固定尺寸的「槽纹理」,
6
+ * 仍按「每全景 N 槽」的接口喂 shader ⇒ **着色器零改动**。
7
+ * ② 2026-10-06(皇上旨,逐字):
8
+ * 「我想的是, 不用 4 片拼合是 canvas 2D 的 drawImage 来拼合, 而是用一个 shader 来一个一个的渲染后的
9
+ * RT 上面去才对」
10
+ * 「把全景重新设计成 8*8 的 64 片的方案, 上传的全景图分割成 8*8=64 片瓦片, 每个 RT 有 4*4=16 片瓦片
11
+ * 组成, 每一帧同样也是只载入一张新纹理到 GPU 里面去, 如果是已经载入过的纹理, 才可以一次性全部渲染
12
+ * 到 RT 上面去」
13
+ * 「canvas 是什么意思, 按照我的想法, 不应该有 canvas 才对啊」⇒ canvas 2D 那一份实现**连同
14
+ * `?panoStitch=canvas` 开关整条删除**,两份实现的接口 `PanoStitcher` 也退回成一个类。
15
+ * ③ 2026-10-06 晚(皇上旨,逐字):「目前 shader 里面一次只展示 4 个全景纹理有点少了, 我希望一次能够
16
+ * 展示 8 个全景纹理, 每个全景纹理由左右 2 个 RT 组成, 每个 RT 由 16 个全景瓦片组成, 所以, 全景图在
17
+ * 上传的时候, 只需要拆分成 横 8 * 竖 4 = 32 个瓦片图就可以了」
18
+ * ⇒ 旨②的 8×8=64 片 / 2×2 四槽 **作废**:改 **8×4=32 片 / 左右 2 槽**(槽纹理跟着变 4096² 正方形)。
19
+ * ⭐ 净效果:sampler 总数仍 16(硬顶)⇒ 同屏全景数 **4 → 8**;画质与单全景显存**分毫未动**;
20
+ * 单全景下载片数 64 → 32(**减半**)。旧存档 64 条地址**不兼容**,皇上「有异常我会重传」。
21
+ *
22
+ * ⚠️ **仍然有 canvas,但只在上传切图那一步**(`src/views/MktMeshEdit/panoramaUpload.ts` 用 2D 画布把原图
23
+ * 切成 32 个 JPEG 传 OSS)—— 那是**离线、一次性、产出的是数据**;运行时这条路上一个 canvas 都没有。
24
+ *
25
+ * ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
26
+ * 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 32 条 XHR 一起炸。
27
+ * 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
28
+ * 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
29
+ * 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
30
+ * 到 GPU 里面去」)。这一趟**只上传、不画** ⇒ 上传与绘制永不落在同一拍(09-29 那句「两个大头别在同一
31
+ * 帧」的延续),而且第 2 步天然只能画「上一帧及更早」传上去的图 ⇒ 每帧 = 1 上传 + 1 绘制(稳态)。
32
+ *
33
+ * ⚠️ 于是「一帧一片」从最旧版的 16 帧/组变成 **32 帧/组**(30ms/帧 ⇒ 全量到位约 1.0s,最旧版约 0.5s;
34
+ * 10-06 白天那版 64 片是 64 帧/组 ≈ 1.9s,现砍半)。每格像素 1024²(旧 1024×512)。
35
+ * 且因为下面那条「先喂 shader」的旨意,第一格画完墙上就有东西了,不必等 16 帧。
36
+ *
37
+ * ⭐ 部分就绪就先喂(同日旨「1. 需要先喂给 shader」):`peek()` 不再等一个槽的 16 格画满,
38
+ * **画过 ≥1 格就交出去**。为了保证交出去的那张 RT 里绝无未定义像素,RT 建好后的第一件事是
39
+ * 整张涂成缺失灰(`_clearRt`)⇒ 观感 = 该象限从左上角起逐格显影,缺数据的格子一直是灰的。
40
+ *
41
+ * ── 画质四条(⛔ 改任何一条都是改画质,先请旨)──────────────────────────────
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` 那条注释)。
46
+ * 2. **色彩**:瓦片纹理 `colorSpace = SRGBColorSpace` ⇒ WebGL2 内部格式 `SRGB8_ALPHA8`,采样时硬件解码成
47
+ * linear;RT 纹理同样标 sRGB ⇒ 写入时硬件再编码回 sRGB。一进一出**逐字节还原**。
48
+ * ⚠️ 另一半证据在 three 里:写进非 XR 的 render target 时片元末尾的 `linearToOutputTexel` 恒按
49
+ * LinearSRGB(= 不转换),编码完全交给硬件 ⇒ ⛔ 我们的片元绝不 `#include <colorspace_fragment>`,
50
+ * 也不带 tone mapping 的 chunk(那两处都会再转一遍 ⇒ 画面发白)。
51
+ * 3. **摩尔纹**:两层都开 mipmap ——
52
+ * ① 瓦片纹理 `generateMipmaps` + `LinearMipmapLinearFilter`:一片原图 1488px 缩进 1024px 的格子是
53
+ * **缩小**,⛔ 单点双线性会采出锯齿(WebGL2 对 NPOT 也能生成 mipmap,three r163 起只支持 WebGL2)。
54
+ * ② 槽 RT 标 `generateMipmaps`:three 每次渲染进 RT 后自己 `glGenerateMipmap`(`WebGLRenderer.js`
55
+ * `updateRenderTargetMipmap`)⇒ 与当年 CanvasTexture 上传后生成 mipmap 同一口径。
56
+ * ⚠️ 代价:每格一次(旧版每格也是一次),都在 GPU 上、不占主线程。
57
+ * 4. **接缝**:槽内 4×4 = 16 格 ⇒ 一张槽内部的格子线从 1 横 1 竖变成 3 横 3 竖。治法 = 上传时每片向四边**多切**
58
+ * `RT_STITCH_SEAM_MARGIN_PX` 像素、拼合时**只采中间那块**(`uSrcRect`)⇒ 边界插值拿得到真邻居像素,
59
+ * 不再复制自己的边缘。⛔ 这条不许「顺手简化」成直接铺满格子:几何会挤歪,缝也还在。
60
+ * 详见 `./mktShader` 里那三个 `rtStitch*` 纯函数 —— **上传侧与拼合侧必须共用同一份几何**。
61
+ *
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/拼合器。
69
+ *
70
+ * ⚠️ RT 是 GL 上下文私有资源:`pump(renderer)` 换 renderer 时整组作废(`_switchRenderer`),下一帧消费方
71
+ * 照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存重新下载)。现况是**每个持有者一份拼合器 + 一个 renderer**
72
+ * (编辑器 `this.renderer`、库里 `onBeforeRender` 捕获的 `_glRenderer`、预览自建渲染器各一份)⇒ 异常才走到。
73
+ *
74
+ * ⭐ 运行中**不淘汰任何组**(10-02 旨「纹理都缓存在 mktmesh / mktdevmesh 对象里面,执行 dispose 时才销毁」):
75
+ * 组只在 `dispose()` / `disposeGroup()` 时才销毁 ⇒ 一片瓦片终身只下载、只解码一次。
76
+ */
77
+
78
+ import {
79
+ ClampToEdgeWrapping,
80
+ DataTexture,
81
+ LinearFilter,
82
+ LinearMipmapLinearFilter,
83
+ Mesh,
84
+ NoBlending,
85
+ OrthographicCamera,
86
+ PlaneGeometry,
87
+ RGBAFormat,
88
+ Scene,
89
+ ShaderMaterial,
90
+ SRGBColorSpace,
91
+ Texture,
92
+ UnsignedByteType,
93
+ Vector3,
94
+ Vector4,
95
+ WebGLRenderTarget,
96
+ type WebGLRenderer,
97
+ } from "three";
98
+ import { loadPanoTexture, releasePanoTexture } from "./panoBitmapTexture";
99
+ import {
100
+ MISSING_TILE_GRAY,
101
+ RT_STITCH_CELLS_PER_SLOT,
102
+ RT_STITCH_SLOT_COUNT,
103
+ RT_STITCH_SLOT_H,
104
+ RT_STITCH_SLOT_TILES,
105
+ RT_STITCH_SLOT_W,
106
+ RT_STITCH_SLOT_GRID,
107
+ RT_STITCH_TILES_PER_BATCH,
108
+ rtStitchBatchOf,
109
+ rtStitchCellPos,
110
+ rtStitchTileInset,
111
+ } from "./mktShader";
112
+
113
+ /** 每帧最多几张**新**纹理进 GPU(旨②「每一帧同样也是只载入一张新纹理到 GPU 里面去」)*/
114
+ const PANO_STITCH_UPLOADS_PER_FRAME = 1;
115
+
116
+ /** 每帧最多发起几张下载(32 片一批,⛔ 不加以限制就是一帧甩 32 条 XHR + 32 次解码) */
117
+ const PANO_STITCH_DOWNLOADS_PER_FRAME = 2;
118
+
119
+ /** 「下载中」的片数上限(跨所有批次) */
120
+ const PANO_STITCH_MAX_INFLIGHT = 8;
121
+
122
+ /** 「已解码、还没上传进 GPU」的片数上限 ⇒ 也是 CPU 侧位图的峰值内存闸(≈8 × 4.6MB) */
123
+ const PANO_STITCH_MAX_DECODED_AHEAD = 8;
124
+
125
+ /** 瓦片纹理的实际像素宽高(`loadPanoTexture` 交回来的是 `ImageBitmap`,尺寸就是切片区)。
126
+ * 内缩比例按**实际尺寸**算 ⇒ 编码差 1px 也不会露出邻居格子的像素。拿不到尺寸时退回下面那个兜底边长。 */
127
+ function texW(tex: Texture): number {
128
+ const img = tex.image as { width?: number } | null | undefined;
129
+ return (img && img.width) || TEX_DIM_FALLBACK;
130
+ }
131
+ function texH(tex: Texture): number {
132
+ const img = tex.image as { height?: number } | null | undefined;
133
+ return (img && img.height) || TEX_DIM_FALLBACK;
134
+ }
135
+ /** 尺寸问不到时的兜底边长:给一个足够大的数 ⇒ 内缩比例趋 0 ⇒ 最坏退化成「不内缩」(= 加边距之前的老行为),
136
+ * 而不是把画面按比例挤歪。 */
137
+ const TEX_DIM_FALLBACK = 100000;
138
+
139
+ /** 缺格涂灰(09-29 晚旨):地址与 shader 那一路同一个常量,这里换成「sRGB 0~1 三分量」喂给 shader,
140
+ * 片元里 sRGB→linear,再由硬件写 RT 时编码回同一字节 ⇒ 与 `uFaceMissingColor` 的观感对齐。 */
141
+ function grayToSrgbVec3(hex: string): Vector3 {
142
+ const s = hex.replace("#", "");
143
+ const byte = (i: number) => parseInt(s.slice(i, i + 2), 16) / 255;
144
+ return new Vector3(byte(0), byte(2), byte(4));
145
+ }
146
+
147
+ /** 全屏 quad 的片元:只干「把一片瓦片的**中间那块**原样搬进 RT 的一格」。
148
+ * ⛔ 不加色彩转换、不加 tone mapping(见文件头画质第 2 条)。 */
149
+ const STITCH_VERT = /* glsl */ `
150
+ varying vec2 vUv;
151
+ void main() {
152
+ vUv = uv;
153
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
154
+ }
155
+ `;
156
+
157
+ const STITCH_FRAG = /* glsl */ `
158
+ uniform sampler2D uMap;
159
+ uniform float uMissing;
160
+ uniform vec3 uMissingSrgb;
161
+ uniform vec4 uSrcRect; // xy = 采样窗口左下、zw = 右上(该瓦片纹理的 UV);窗口外那圈就是多切的接缝余量
162
+ varying vec2 vUv;
163
+ void main() {
164
+ if ( uMissing > 0.5 ) {
165
+ vec3 c = ( uMissingSrgb + 0.055 ) / 1.055;
166
+ gl_FragColor = vec4( pow( c, vec3( 2.4 ) ), 1.0 );
167
+ return;
168
+ }
169
+ vec2 suv = mix( uSrcRect.xy, uSrcRect.zw, vUv );
170
+ gl_FragColor = vec4( texture2D( uMap, suv ).rgb, 1.0 );
171
+ }
172
+ `;
173
+
174
+ /** 一片瓦片的在册状态(`_texs` 的条目)。`tex = null` ⇒ 还在下载/解码。 */
175
+ interface TileRec {
176
+ tex: Texture | null;
177
+ /** 已 `renderer.initTexture` 过 ⇒ 画它不会再顺手触发 `texImage2D`(`_drawPass` 的唯一准入判据) */
178
+ uploaded: boolean;
179
+ }
180
+
181
+ /** 一个 32 瓦片批次(= 一个全景点)的拼合组 */
182
+ interface StitchGroup {
183
+ /** 32 张瓦片地址(原图 **横 8 × 竖 4 行优先**、图像左上起;不足 32 条补空串占位 ⇒ 缺格涂灰) */
184
+ urls: string[];
185
+ /** `RT_STITCH_SLOT_COUNT`(2) 个槽的渲染目标(懒建;`rt.texture` 就是交给 shader 的那张槽纹理) */
186
+ rts: (WebGLRenderTarget | null)[];
187
+ /** 每槽一个 16 位掩码:bit `cell` = 1 ⇒ 这一格已画进 RT(**允许跳着画**:谁先到就上谁) */
188
+ drawn: number[];
189
+ /** 每槽已画格数(= 掩码的 popcount,省得每帧数) */
190
+ drawnCount: number[];
191
+ /** 还剩几槽没画满 */
192
+ pending: number;
193
+ }
194
+
195
+ /**
196
+ * 拼合器对外接口(消费方 `MktMesh` / 编辑器渲染器 / 预览只认这一个类)。
197
+ * 10-06 之前它是「两份实现共用一份接口」里的 canvas 那一份 ⇒ 那份已奉旨删除,接口留在原地当文档。
198
+ */
199
+ export interface PanoStitcher {
200
+ /** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做) */
201
+ ensure(urls: string[]): void;
202
+ /** 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」);没画过 / 没这组返回 null。 */
203
+ peek(batch: string, slot: number): Texture | null;
204
+ /** 每帧驱动(消费方每帧只调一次)。`renderer` = 调用方当前的 WebGLRenderer,还没捕获到就传 null
205
+ * ⇒ 这一帧什么都不画(⛔ 不打假读数,也⛔ 不退回任何 CPU 拼合路径)。 */
206
+ pump(renderer: WebGLRenderer | null): void;
207
+ /** 这张槽纹理是否**已经在 GPU 上**(RT 版恒 true):消费方据此跳过「每 tick 一张」预上传闸。
208
+ * ⚠️ 返回 true 的纹理绝不能喂给 `renderer.initTexture` —— 它不是普通纹理,是 three 自己管的渲染目标附件。 */
209
+ resident(tex: Texture): boolean;
210
+ /** 该批次是否已在册(诊断/测试用) */
211
+ has(batch: string): boolean;
212
+ /** 该批次已画进 RT 的格数(0~32)⇒ 编辑器按需渲染的「指纹」用它判断拼合还在推进 */
213
+ cellsDrawn(batch: string): number;
214
+ /** 全局推进计数(画一格 / 传一张 / 建组 / 拆组都 +1)⇒ 上面那个指纹的单调哨兵,无需知道批次名 */
215
+ progressStamp(): number;
216
+ /** 释放一整组(含它的 GPU 资源) */
217
+ disposeGroup(batch: string): void;
218
+ /** 释放全部 */
219
+ dispose(): void;
220
+ }
221
+
222
+ export class PanoTileStitcher implements PanoStitcher {
223
+ /** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
224
+ private _groups: Map<string, StitchGroup> = new Map();
225
+ /** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
226
+ private _texs: Map<string, TileRec> = new Map();
227
+ /** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
228
+ private _wanted: Set<string> = new Set();
229
+ /** 加载失败退避(url → 最早可重试时间戳)/ 失败计数 / 死链 */
230
+ private _retryAt: Map<string, number> = new Map();
231
+ private _fails: Map<string, number> = new Map();
232
+ private _dead: Set<string> = new Set();
233
+ /** 建 RT 时用的渲染器(换 renderer ⇒ 在册组全清,见文件头) */
234
+ private _renderer: WebGLRenderer | null = null;
235
+ /** 推进计数(`progressStamp`)*/
236
+ private _stamp = 0;
237
+
238
+ // 拼合用的那一套极小的 scene(所有组共用:一个正交相机把 [-1,1]² 铺满整张 RT,一个 quad 摆到目标格子)
239
+ private _scene: Scene = new Scene();
240
+ private _camera: OrthographicCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
241
+ private _mat: ShaderMaterial = new ShaderMaterial({
242
+ vertexShader: STITCH_VERT,
243
+ fragmentShader: STITCH_FRAG,
244
+ uniforms: {
245
+ uMap: { value: null },
246
+ uMissing: { value: 0 },
247
+ uMissingSrgb: { value: grayToSrgbVec3(MISSING_TILE_GRAY) },
248
+ uSrcRect: { value: new Vector4(0, 0, 1, 1) },
249
+ },
250
+ depthTest: false,
251
+ depthWrite: false,
252
+ blending: NoBlending,
253
+ });
254
+ /** 几何是 2×2(铺满正交相机的 [-1,1]²),每格只把它缩到 1/4 再挪过去 ⇒ 一格 = 槽的 1/16 */
255
+ private _quad: Mesh<PlaneGeometry, ShaderMaterial> = new Mesh(new PlaneGeometry(2, 2), this._mat);
256
+ /** 缺格那一路 `uMap` 也要有个合法纹理可绑(1×1,从不真采样) */
257
+ private _blank: DataTexture = new DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
258
+ /** 本拼合器发出去的槽纹理 = RT 附件(`resident()` 认它);⛔ 不许喂给 `renderer.initTexture` */
259
+ private _rtTextures: WeakSet<Texture> = new WeakSet();
260
+
261
+ constructor() {
262
+ this._quad.frustumCulled = false; // 正交相机 + 手工摆位,⛔ 别按包围球把格子剔没了
263
+ this._scene.add(this._quad);
264
+ this._blank.needsUpdate = true;
265
+ }
266
+
267
+ /** 确保批次已建组、加载已发起。幂等:同批已在册 ⇒ 直接返回(一片瓦片终身只下载、只解码一次)。
268
+ * ⭐ 09-29 晚旨「不足的涂灰」+ 10-06 晚旨「8×4 = 32 片」:1~32 条皆可入组,内部补空串占位到 32;
269
+ * 空串 / 死链的格子拼合时涂灰(一眼看出该全景数据不全)。
270
+ * ⛔ 旧版 16 条(4×4)与 64 条(8×8)的数据**都不做兼容**(皇上「无须兼容正常使用, 有异常我会重传」):
271
+ * 64 条会在上一行被判超限直接丢弃;真喂进来的短数据只占前几格 + 其余涂灰,那是**故意**让数据不全看得见。
272
+ * ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null,null,null]` —— 槽数再变会漏改)。 */
273
+ ensure(urls: string[]): void {
274
+ if (!urls || urls.length === 0 || urls.length > RT_STITCH_TILES_PER_BATCH) return;
275
+ const u = urls.slice();
276
+ while (u.length < RT_STITCH_TILES_PER_BATCH) u.push("");
277
+ const batch = rtStitchBatchOf(u);
278
+ if (this._groups.has(batch)) return; // ⭐ 已建组:什么都不做(绝不重建 ⇒ 永不二次解码)
279
+ this._groups.set(batch, {
280
+ urls: u,
281
+ rts: Array.from({ length: RT_STITCH_SLOT_COUNT }, () => null),
282
+ drawn: Array.from({ length: RT_STITCH_SLOT_COUNT }, () => 0),
283
+ drawnCount: Array.from({ length: RT_STITCH_SLOT_COUNT }, () => 0),
284
+ pending: RT_STITCH_SLOT_COUNT,
285
+ });
286
+ this._stamp++;
287
+ }
288
+
289
+ /** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 16 格画满)。
290
+ * RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
291
+ peek(batch: string, slot: number): Texture | null {
292
+ const g = this._groups.get(batch);
293
+ if (!g || !g.drawnCount[slot]) return null;
294
+ const rt = g.rts[slot];
295
+ return rt ? rt.texture : null;
296
+ }
297
+
298
+ /** ⭐ 这条路交出去的槽纹理本来就是 GL 渲染目标的附件 —— 像素已经在显存里,没有「上传」这一步。
299
+ * 消费方(`MktMesh._peekTileAt` / 编辑器 `_peekTileAt`)据此**跳过**「每 tick 一张 `initTexture`」那道闸。 */
300
+ resident(tex: Texture): boolean {
301
+ return this._rtTextures.has(tex);
302
+ }
303
+
304
+ has(batch: string): boolean {
305
+ return this._groups.has(batch);
306
+ }
307
+
308
+ cellsDrawn(batch: string): number {
309
+ const g = this._groups.get(batch);
310
+ if (!g) return 0;
311
+ return g.drawnCount.reduce((s, n) => s + n, 0);
312
+ }
313
+
314
+ progressStamp(): number {
315
+ return this._stamp;
316
+ }
317
+
318
+ /** 每帧驱动(三道工序,顺序固定:补下载 → 画已进 GPU 的 → 再传一张新的)。 */
319
+ pump(renderer: WebGLRenderer | null): void {
320
+ this._pumpRetries();
321
+ this._pumpDownloads();
322
+ if (!renderer) return;
323
+ if (this._renderer !== renderer) this._switchRenderer(renderer);
324
+ this._drawPass(renderer);
325
+ this._uploadPass(renderer);
326
+ }
327
+
328
+ /** 释放整组(2 张 RT + 该组还没画掉的瓦片纹理) */
329
+ disposeGroup(batch: string): void {
330
+ const g = this._groups.get(batch);
331
+ if (!g) return;
332
+ this._dropGroupGpu(g);
333
+ this._groups.delete(batch);
334
+ this._stamp++;
335
+ }
336
+
337
+ /** 释放全部(持有者 `dispose()` 时调用);共享的那套 quad/材质/空白纹理一并退掉 */
338
+ dispose(): void {
339
+ for (const batch of [...this._groups.keys()]) this.disposeGroup(batch);
340
+ this._texs.clear();
341
+ this._wanted.clear();
342
+ this._retryAt.clear();
343
+ this._fails.clear();
344
+ this._dead.clear();
345
+ this._quad.geometry.dispose();
346
+ this._mat.dispose();
347
+ this._blank.dispose();
348
+ this._scene.remove(this._quad);
349
+ }
350
+
351
+ // ───────────────────────── 内部:每帧三道工序 ─────────────────────────
352
+
353
+ private _now(): number {
354
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
355
+ }
356
+
357
+ /** 工序 1:按「槽 0 格 0 → 槽 3 格 15」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 /
358
+ * 下载中 ≤8 / 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。 */
359
+ private _pumpDownloads(): void {
360
+ let inflight = 0;
361
+ let decoded = 0;
362
+ for (const rec of this._texs.values()) {
363
+ if (rec.tex) decoded++;
364
+ else inflight++;
365
+ }
366
+ let budget = PANO_STITCH_DOWNLOADS_PER_FRAME;
367
+ for (const g of this._groups.values()) {
368
+ if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
369
+ for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
370
+ const ids = RT_STITCH_SLOT_TILES[slot];
371
+ for (let cell = 0; cell < RT_STITCH_CELLS_PER_SLOT; cell++) {
372
+ if (g.drawn[slot] & (1 << cell)) continue; // 画过的格子那片早释放了,不用再来
373
+ const url = g.urls[ids[cell]];
374
+ if (!url || this._dead.has(url)) continue; // 缺格 / 死链:涂灰那一路,不需要纹理
375
+ if (this._texs.has(url)) continue; // 已在册(下载中或已解码)⇒ ⛔ 发第二遍
376
+ this._loadTile(url);
377
+ inflight++;
378
+ if (--budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
379
+ }
380
+ }
381
+ }
382
+ }
383
+
384
+ /** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
385
+ * ⛔ 准入只看一件事:`uploaded`。没上传过的纹理当场画 = `texImage2D` 藏进 `render()` 里
386
+ * = 每帧一张新纹理那道闸形同虚设(这正是本轮要治的卡顿本体)。 */
387
+ private _drawPass(renderer: WebGLRenderer): void {
388
+ for (const g of this._groups.values()) {
389
+ if (g.pending <= 0) continue;
390
+ for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
391
+ if (g.drawnCount[slot] >= RT_STITCH_CELLS_PER_SLOT) continue;
392
+ const ids = RT_STITCH_SLOT_TILES[slot];
393
+ for (let cell = 0; cell < RT_STITCH_CELLS_PER_SLOT; cell++) {
394
+ if (g.drawn[slot] & (1 << cell)) continue;
395
+ const url = g.urls[ids[cell]];
396
+ if (!url || this._dead.has(url)) {
397
+ this._stitchCell(renderer, g, slot, cell, null); // 缺格:涂灰,不占任何预算
398
+ continue;
399
+ }
400
+ const rec = this._texs.get(url);
401
+ if (!rec || !rec.tex || !rec.uploaded) continue; // 还没上 GPU:等下一帧(工序 3 会排它)
402
+ this._stitchCell(renderer, g, slot, cell, rec.tex);
403
+ this._dropTile(url); // 一格只用这一片 ⇒ 画完即弃(显存峰值 = 一格)
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ /** 工序 3:每帧至多把**一张**新纹理传进 GPU(旨②),这一趟只上传、不画。
410
+ * 按「槽 0 格 0 → …」的顺序挑第一张已解码没上传的 ⇒ RT 从左上角起逐格显影,观感可预期。 */
411
+ private _uploadPass(renderer: WebGLRenderer): void {
412
+ let budget = PANO_STITCH_UPLOADS_PER_FRAME;
413
+ for (const g of this._groups.values()) {
414
+ if (g.pending <= 0) continue;
415
+ for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
416
+ const ids = RT_STITCH_SLOT_TILES[slot];
417
+ for (let cell = 0; cell < RT_STITCH_CELLS_PER_SLOT; cell++) {
418
+ if (g.drawn[slot] & (1 << cell)) continue;
419
+ const url = g.urls[ids[cell]];
420
+ if (!url || this._dead.has(url)) continue;
421
+ const rec = this._texs.get(url);
422
+ if (!rec || !rec.tex || rec.uploaded) continue;
423
+ this._uploadTile(rec);
424
+ if (--budget <= 0) return; // 旨②「每一帧只载入一张新纹理」⇒ 这一趟到此为止
425
+ }
426
+ }
427
+ }
428
+ }
429
+
430
+ /** 上传一片瓦片的纹理(`texImage2D` + mipmap 才是那一下贵的,所以单独成工序)。
431
+ * ⚠️ 成败都记 `uploaded`:不记 ⇒ 那一格永远进不了 `_drawPass` ⇒ 该槽永远画不满、每帧白烧一个预算,
432
+ * 比「那一帧没错峰」严重得多;记了最坏也只是退回 three 在绑定那一帧自己上传的老路径。
433
+ * (与 `MktMesh._uploadTileTexture` 同一口径,皇上旨 2026-09-25「队列里只放上传那一句」。) */
434
+ private _uploadTile(rec: TileRec): void {
435
+ const renderer = this._renderer;
436
+ if (!renderer || !rec.tex) return;
437
+ try {
438
+ renderer.initTexture(rec.tex);
439
+ } catch (e) {
440
+ console.warn("[PanoTileStitcher] 瓦片预上传失败,交给绘制那一帧自己上传:", e);
441
+ }
442
+ rec.uploaded = true;
443
+ this._stamp++;
444
+ }
445
+
446
+ // ───────────────────────── 内部:单格绘制 ─────────────────────────
447
+
448
+ /** 画一格:把那片瓦片的**中间那块**铺进该槽第 `cell` 格(16 格里的一格)。
449
+ * ⚠️⚠️ `autoClear` 必须关掉:three 每次 `render()` 默认清屏,而这里**同一张 RT 要连画 16 帧**,
450
+ * 清一次就把前面画好的格子抹了 ⇒ 槽纹理只剩最后一格。清屏交给我们自己(见 `_clearRt`)。 */
451
+ private _stitchCell(renderer: WebGLRenderer, g: StitchGroup, slot: number, cell: number, tex: Texture | null): void {
452
+ const rt = this._ensureRt(renderer, g, slot);
453
+ const tileIdx = RT_STITCH_SLOT_TILES[slot][cell];
454
+ const [cc, rr] = rtStitchCellPos(cell);
455
+ // 正交相机铺满 [-1,1]²:quad 缩到 1/4 ⇒ 占一格;`rtStitchCellPos` 是**图像方向**的 [列, 行],
456
+ // GL 的 y 向上 ⇒ 图像第一行落在 RT 的上半(v=1),故 y 取反。
457
+ this._quad.scale.set(1 / RT_STITCH_SLOT_GRID, 1 / RT_STITCH_SLOT_GRID, 1);
458
+ this._quad.position.set(
459
+ (2 * cc + 1) / RT_STITCH_SLOT_GRID - 1,
460
+ 1 - (2 * rr + 1) / RT_STITCH_SLOT_GRID,
461
+ 0,
462
+ );
463
+ this._mat.uniforms.uMap.value = tex ?? this._blank;
464
+ this._mat.uniforms.uMissing.value = tex ? 0 : 1;
465
+ const rect = tex ? rtStitchTileInset(tileIdx, texW(tex), texH(tex)) : null;
466
+ const uv = this._mat.uniforms.uSrcRect.value as Vector4;
467
+ if (rect) uv.set(rect[0], rect[1], rect[2], rect[3]);
468
+ else uv.set(0, 0, 1, 1);
469
+ this._renderQuad(renderer, rt);
470
+ g.drawn[slot] |= 1 << cell;
471
+ g.drawnCount[slot]++;
472
+ this._stamp++;
473
+ if (g.drawnCount[slot] >= RT_STITCH_CELLS_PER_SLOT) g.pending--;
474
+ if (g.pending <= 0) this._releaseGroupWanted(g);
475
+ }
476
+
477
+ /** 把整张 RT 涂成缺失灰:RT 建好后的第一件事 ⇒ 保证「画不满也先喂给 shader」时**没有未定义像素**。
478
+ * 走的是与缺格同一条 shader 路径(同一个 sRGB→linear→硬件编码回 sRGB)⇒ 灰度字节与涂灰格子完全相同。 */
479
+ private _clearRt(renderer: WebGLRenderer, rt: WebGLRenderTarget): void {
480
+ this._quad.scale.set(1, 1, 1);
481
+ this._quad.position.set(0, 0, 0);
482
+ this._mat.uniforms.uMap.value = this._blank;
483
+ this._mat.uniforms.uMissing.value = 1;
484
+ (this._mat.uniforms.uSrcRect.value as Vector4).set(0, 0, 1, 1);
485
+ this._renderQuad(renderer, rt);
486
+ }
487
+
488
+ /** 唯一碰 `renderer` 状态的那一句:存现场 → 关掉自动清屏 → 画 → 还原。 */
489
+ private _renderQuad(renderer: WebGLRenderer, rt: WebGLRenderTarget): void {
490
+ const prevTarget = renderer.getRenderTarget();
491
+ const prevAutoClear = renderer.autoClear;
492
+ renderer.autoClear = false;
493
+ renderer.setRenderTarget(rt);
494
+ renderer.render(this._scene, this._camera);
495
+ renderer.setRenderTarget(prevTarget);
496
+ renderer.autoClear = prevAutoClear;
497
+ }
498
+
499
+ // ───────────────────────── 内部:瓦片加载 ─────────────────────────
500
+
501
+ /** 发起一片瓦片的下载 + 异步解码(`loadPanoTexture` ⇒ 解码在工作线程,主线程只拿结果)。
502
+ * 幂等;失败记 3s 退避重发,累计 3 次判死链 ⇒ 那一格涂灰,一眼看出该全景数据不全。 */
503
+ private _loadTile(url: string): void {
504
+ if (!url || this._texs.has(url) || this._retryAt.has(url) || this._dead.has(url)) return;
505
+ this._wanted.add(url);
506
+ this._texs.set(url, { tex: null, uploaded: false });
507
+ loadPanoTexture(url).then(
508
+ (tex) => {
509
+ // 与消费方那两条(`MktMesh.ensureTileTexture` / 编辑器 `ensureFloorTexture`)同一套纹理参数
510
+ tex.colorSpace = SRGBColorSpace;
511
+ tex.minFilter = LinearMipmapLinearFilter; // 缩小进格子要 mipmap,⛔ 单点双线性出锯齿
512
+ tex.magFilter = LinearFilter;
513
+ tex.generateMipmaps = true;
514
+ tex.wrapS = ClampToEdgeWrapping;
515
+ tex.wrapT = ClampToEdgeWrapping;
516
+ tex.needsUpdate = true;
517
+ const rec = this._texs.get(url);
518
+ if (!this._wanted.has(url) || !rec) {
519
+ // 迟到的回调:这一组已经作废(换 renderer / dispose)⇒ 当场释放,别留一份没人认领的显存 + 位图
520
+ releasePanoTexture(tex);
521
+ return;
522
+ }
523
+ this._retryAt.delete(url);
524
+ this._fails.delete(url);
525
+ rec.tex = tex; // uploaded 仍 false ⇒ 下一帧的工序 2/3 才轮得到它
526
+ },
527
+ () => this._failTile(url),
528
+ );
529
+ }
530
+
531
+ /** 加载失败:摘掉「加载中」占位;3 次判死链,否则 3s 退避由 `_pumpRetries` 重发 */
532
+ private _failTile(url: string): void {
533
+ this._texs.delete(url);
534
+ this._wanted.delete(url);
535
+ const n = (this._fails.get(url) || 0) + 1;
536
+ this._fails.set(url, n);
537
+ if (n >= 3) {
538
+ this._retryAt.delete(url);
539
+ this._dead.add(url);
540
+ } else {
541
+ this._retryAt.set(url, this._now() + 3000);
542
+ }
543
+ }
544
+
545
+ /** 到点的失败重发(每帧最多 2 个,防风暴) */
546
+ private _pumpRetries(): void {
547
+ if (this._retryAt.size === 0) return;
548
+ let budget = 2;
549
+ const now = this._now();
550
+ for (const [url, at] of [...this._retryAt]) {
551
+ if (budget <= 0) break;
552
+ if (now < at) continue;
553
+ this._retryAt.delete(url);
554
+ this._loadTile(url);
555
+ budget--;
556
+ }
557
+ }
558
+
559
+ /** 换 renderer:RT 是旧上下文的私有资源,留着既画不了也释放不掉 ⇒ 在册组整份作废,
560
+ * 靠消费方每帧照旧的 `ensure()` 重建(瓦片走浏览器 HTTP 缓存,不重新编码)。 */
561
+ private _switchRenderer(renderer: WebGLRenderer): void {
562
+ this._renderer = renderer;
563
+ for (const batch of [...this._groups.keys()]) this.disposeGroup(batch);
564
+ this._texs.clear();
565
+ this._wanted.clear();
566
+ this._retryAt.clear();
567
+ this._fails.clear();
568
+ this._dead.clear();
569
+ }
570
+
571
+ /** 懒建某槽的渲染目标:4096×4096 RGBA8 + sRGB + mipmap,并**立刻整张涂缺失灰**
572
+ * (⛔ 未定义像素不许进 shader:`peek()` 现在画过 1 格就交出去)。 */
573
+ private _ensureRt(renderer: WebGLRenderer, g: StitchGroup, slot: number): WebGLRenderTarget {
574
+ const exist = g.rts[slot];
575
+ if (exist) return exist;
576
+ const rt = new WebGLRenderTarget(RT_STITCH_SLOT_W, RT_STITCH_SLOT_H, {
577
+ wrapS: ClampToEdgeWrapping,
578
+ wrapT: ClampToEdgeWrapping,
579
+ minFilter: LinearMipmapLinearFilter,
580
+ magFilter: LinearFilter,
581
+ format: RGBAFormat,
582
+ type: UnsignedByteType,
583
+ colorSpace: SRGBColorSpace,
584
+ generateMipmaps: true,
585
+ depthBuffer: false,
586
+ stencilBuffer: false,
587
+ });
588
+ g.rts[slot] = rt;
589
+ this._rtTextures.add(rt.texture);
590
+ this._clearRt(renderer, rt);
591
+ return rt;
592
+ }
593
+
594
+ /** 释放一片瓦片的纹理 + ImageBitmap(它只属于一格,画完即弃 ⇒ 显存峰值 = 一格) */
595
+ private _dropTile(url: string): void {
596
+ const rec = this._texs.get(url);
597
+ if (rec && rec.tex) releasePanoTexture(rec.tex);
598
+ this._texs.delete(url);
599
+ this._wanted.delete(url);
600
+ }
601
+
602
+ /** 整组拼完:该组已无在册地址(瓦片逐片释放过了),清 `_wanted` 让迟到回调自己收尾 */
603
+ private _releaseGroupWanted(g: StitchGroup): void {
604
+ for (const u of g.urls) this._wanted.delete(u);
605
+ }
606
+
607
+ /** 释放一组的全部 GPU 资源:2 张 RT + 还没画掉的瓦片纹理 */
608
+ private _dropGroupGpu(g: StitchGroup): void {
609
+ for (let slot = 0; slot < RT_STITCH_SLOT_COUNT; slot++) {
610
+ const rt = g.rts[slot];
611
+ if (rt) rt.dispose();
612
+ g.rts[slot] = null;
613
+ }
614
+ for (const u of g.urls) if (u) this._dropTile(u);
615
+ }
616
+ }