framefields 2.13.0 → 2.14.0

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 (302) hide show
  1. package/README.md +9 -4
  2. package/dist/audio/index.d.mts +5 -5
  3. package/dist/audio/index.mjs +10 -10
  4. package/dist/{audio-CHV33Od9.d.mts → audio-CdCuGK5d.d.mts} +3 -3
  5. package/dist/{audio-CHV33Od9.d.mts.map → audio-CdCuGK5d.d.mts.map} +1 -1
  6. package/dist/{audio-DHzLQ_RG.mjs → audio-DFwRbID_.mjs} +3 -3
  7. package/dist/{audio-DHzLQ_RG.mjs.map → audio-DFwRbID_.mjs.map} +1 -1
  8. package/dist/cli/check-worker.mjs +43 -43
  9. package/dist/cloud/index.d.mts.map +1 -1
  10. package/dist/components/index.d.mts +13 -13
  11. package/dist/components/index.mjs +42 -42
  12. package/dist/{dist-D3BeAZ7X.mjs → dist--Uu5bQ_6.mjs} +870 -348
  13. package/dist/dist--Uu5bQ_6.mjs.map +1 -0
  14. package/dist/{dist-CUNvNDS3.mjs → dist-B8Nby2CU.mjs} +18 -4
  15. package/dist/dist-B8Nby2CU.mjs.map +1 -0
  16. package/dist/effects/index.d.mts +5 -5
  17. package/dist/effects/index.mjs +34 -34
  18. package/dist/{effects-BiDgCK1L.mjs → effects-WMgv8ZaD.mjs} +2 -2
  19. package/dist/{effects-BiDgCK1L.mjs.map → effects-WMgv8ZaD.mjs.map} +1 -1
  20. package/dist/fonts/index.mjs +2 -2
  21. package/dist/{fonts-DeDPWdvJ.mjs → fonts-OW3yAfO5.mjs} +2 -2
  22. package/dist/{fonts-DeDPWdvJ.mjs.map → fonts-OW3yAfO5.mjs.map} +1 -1
  23. package/dist/{gpu-CWjgql4C.mjs → gpu-R4T2Tndu.mjs} +2 -2
  24. package/dist/{gpu-CWjgql4C.mjs.map → gpu-R4T2Tndu.mjs.map} +1 -1
  25. package/dist/{index-E2IXX0Cz.d.mts → index-9xte4JgL.d.mts} +110 -61
  26. package/dist/index-9xte4JgL.d.mts.map +1 -0
  27. package/dist/{index-YB9j9KUx.d.mts → index-B0Vq8Bwf.d.mts} +3 -3
  28. package/dist/{index-YB9j9KUx.d.mts.map → index-B0Vq8Bwf.d.mts.map} +1 -1
  29. package/dist/{index-BFTtXosj.d.mts → index-BVd6nXd5.d.mts} +2 -2
  30. package/dist/index-BVd6nXd5.d.mts.map +1 -0
  31. package/dist/{index-CnZhBlob.d.mts → index-Bv7gxGK1.d.mts} +2 -2
  32. package/dist/{index-CnZhBlob.d.mts.map → index-Bv7gxGK1.d.mts.map} +1 -1
  33. package/dist/{index-a6JrbAK0.d.mts → index-C-V6CAhP.d.mts} +32 -2
  34. package/dist/index-C-V6CAhP.d.mts.map +1 -0
  35. package/dist/{index-DQ1U3HC3.d.mts → index-C7-p9zwU.d.mts} +3 -3
  36. package/dist/{index-DQ1U3HC3.d.mts.map → index-C7-p9zwU.d.mts.map} +1 -1
  37. package/dist/{index-rtCXNxGO.d.mts → index-CEKxvGY-.d.mts} +2 -2
  38. package/dist/{index-rtCXNxGO.d.mts.map → index-CEKxvGY-.d.mts.map} +1 -1
  39. package/dist/{index-X2u96TEo.d.mts → index-CRaydvLi.d.mts} +29 -11
  40. package/dist/index-CRaydvLi.d.mts.map +1 -0
  41. package/dist/{index-DBh3WphX.d.mts → index-D1FqV1eu.d.mts} +2 -2
  42. package/dist/{index-DBh3WphX.d.mts.map → index-D1FqV1eu.d.mts.map} +1 -1
  43. package/dist/{index-Dibc_xpC.d.mts → index-DoP6EiHj.d.mts} +5 -5
  44. package/dist/{index-Dibc_xpC.d.mts.map → index-DoP6EiHj.d.mts.map} +1 -1
  45. package/dist/{index-D68eGFNE.d.mts → index-GA9_Z8Fn.d.mts} +2 -2
  46. package/dist/{index-D68eGFNE.d.mts.map → index-GA9_Z8Fn.d.mts.map} +1 -1
  47. package/dist/index.d.mts +14 -14
  48. package/dist/index.mjs +50 -50
  49. package/dist/preview/index.d.mts +5 -5
  50. package/dist/preview/index.mjs +10 -10
  51. package/dist/{preview-PDz7o2xg.mjs → preview-D7BGmE1E.mjs} +3 -3
  52. package/dist/{preview-PDz7o2xg.mjs.map → preview-D7BGmE1E.mjs.map} +1 -1
  53. package/dist/preview-engine/audio.mjs +1 -1
  54. package/dist/preview-engine/{chunk-IEEV5RB3.mjs → chunk-3C2XT64C.mjs} +1 -1
  55. package/dist/preview-engine/chunk-52X7SURQ.mjs +494 -0
  56. package/dist/preview-engine/{chunk-XYCR3STG.mjs → chunk-5DZTPUMH.mjs} +198 -92
  57. package/dist/preview-engine/{chunk-4P3MMJWD.mjs → chunk-FZ4RD7HV.mjs} +1 -1
  58. package/dist/preview-engine/chunk-GNDE3OXQ.mjs +5 -0
  59. package/dist/preview-engine/chunk-OBIJIQCZ.mjs +1 -0
  60. package/dist/preview-engine/{chunk-SMHEMYCV.mjs → chunk-UBY2UYEL.mjs} +1 -1
  61. package/dist/preview-engine/{chunk-UFPA36D2.mjs → chunk-UPZURPJY.mjs} +31 -31
  62. package/dist/preview-engine/components.mjs +1 -1
  63. package/dist/preview-engine/effects.mjs +1 -1
  64. package/dist/preview-engine/framefields.mjs +1 -1
  65. package/dist/preview-engine/player.mjs +1 -1
  66. package/dist/preview-engine/renderers-DLJOA3BM.mjs +228 -0
  67. package/dist/preview-engine/signals.mjs +1 -1
  68. package/dist/{program-Bn6ko_I4.mjs → program-tFKCWU2j.mjs} +109 -12
  69. package/dist/program-tFKCWU2j.mjs.map +1 -0
  70. package/dist/renderer/index.d.mts +4 -4
  71. package/dist/renderer/index.mjs +2 -2
  72. package/dist/{renderer-BCTUqJG7.mjs → renderer-03mYJLgX.mjs} +3 -3
  73. package/dist/{renderer-BCTUqJG7.mjs.map → renderer-03mYJLgX.mjs.map} +1 -1
  74. package/dist/{renderer-CyUiXLRa.mjs → renderer-0rveCDm-.mjs} +3 -3
  75. package/dist/{renderer-CyUiXLRa.mjs.map → renderer-0rveCDm-.mjs.map} +1 -1
  76. package/dist/{renderer-BUj7jE6t.mjs → renderer-2PjR3w-q.mjs} +3 -3
  77. package/dist/{renderer-BUj7jE6t.mjs.map → renderer-2PjR3w-q.mjs.map} +1 -1
  78. package/dist/{renderer-BqUAyzCZ.mjs → renderer-47y12Z37.mjs} +2 -2
  79. package/dist/{renderer-BqUAyzCZ.mjs.map → renderer-47y12Z37.mjs.map} +1 -1
  80. package/dist/renderer-6_bzbbXt.mjs +5 -0
  81. package/dist/renderer-8M-9G6F_.mjs +5 -0
  82. package/dist/{renderer-CwlY8Tb-.mjs → renderer-B0yVUOPP.mjs} +2 -2
  83. package/dist/{renderer-CwlY8Tb-.mjs.map → renderer-B0yVUOPP.mjs.map} +1 -1
  84. package/dist/renderer-B5OzKvF8.mjs +5 -0
  85. package/dist/{renderer-CgK7Q1G8.mjs → renderer-B7G1KohJ.mjs} +2 -2
  86. package/dist/renderer-BBCZ2qbE.mjs +5 -0
  87. package/dist/{renderer-CuPAQjDK.mjs → renderer-BBMtmxks.mjs} +3 -3
  88. package/dist/{renderer-CuPAQjDK.mjs.map → renderer-BBMtmxks.mjs.map} +1 -1
  89. package/dist/renderer-BChbxCux.mjs +5 -0
  90. package/dist/{renderer-C-GTxnfv.mjs → renderer-BDzn1AKF.mjs} +2 -2
  91. package/dist/{renderer-C-GTxnfv.mjs.map → renderer-BDzn1AKF.mjs.map} +1 -1
  92. package/dist/renderer-BGeMPr8C.mjs +5 -0
  93. package/dist/{renderer-RzkU_O1i.mjs → renderer-BGp0An5A.mjs} +3 -3
  94. package/dist/{renderer-RzkU_O1i.mjs.map → renderer-BGp0An5A.mjs.map} +1 -1
  95. package/dist/{renderer-BZgJj8Cl.mjs → renderer-BH7UCMPQ.mjs} +2 -2
  96. package/dist/{renderer-BZgJj8Cl.mjs.map → renderer-BH7UCMPQ.mjs.map} +1 -1
  97. package/dist/{renderer-aRk849b3.d.mts → renderer-BHYJlK6H.d.mts} +2 -2
  98. package/dist/{renderer-aRk849b3.d.mts.map → renderer-BHYJlK6H.d.mts.map} +1 -1
  99. package/dist/{renderer-Cu3iKMSJ.mjs → renderer-BJ8ea92W.mjs} +3 -3
  100. package/dist/{renderer-Cu3iKMSJ.mjs.map → renderer-BJ8ea92W.mjs.map} +1 -1
  101. package/dist/{renderer-Dp5ChuOs.mjs → renderer-BOSZAdgF.mjs} +2 -2
  102. package/dist/{renderer-Dp5ChuOs.mjs.map → renderer-BOSZAdgF.mjs.map} +1 -1
  103. package/dist/renderer-BPQ8Pkjb.mjs +5 -0
  104. package/dist/renderer-BTRpA3HD.mjs +5 -0
  105. package/dist/{renderer-vll8LX0I.mjs → renderer-BVIXKfbC.mjs} +132 -116
  106. package/dist/renderer-BVIXKfbC.mjs.map +1 -0
  107. package/dist/{renderer-DpZballM.mjs → renderer-BVU22lBS.mjs} +2 -2
  108. package/dist/{renderer-DpZballM.mjs.map → renderer-BVU22lBS.mjs.map} +1 -1
  109. package/dist/renderer-BVsECSn-.mjs +5 -0
  110. package/dist/{renderer-DwGlDNOV.mjs → renderer-BWSRryIA.mjs} +2 -2
  111. package/dist/{renderer-DwGlDNOV.mjs.map → renderer-BWSRryIA.mjs.map} +1 -1
  112. package/dist/{renderer-BMpmmYOq.mjs → renderer-BacY4nh8.mjs} +2 -2
  113. package/dist/{renderer-BMpmmYOq.mjs.map → renderer-BacY4nh8.mjs.map} +1 -1
  114. package/dist/{renderer-CZ5mwBW1.mjs → renderer-Baw6Utxi.mjs} +2 -2
  115. package/dist/{renderer-CZ5mwBW1.mjs.map → renderer-Baw6Utxi.mjs.map} +1 -1
  116. package/dist/{renderer-NBMmvqWl.mjs → renderer-Bcu3UrI-.mjs} +3 -3
  117. package/dist/{renderer-NBMmvqWl.mjs.map → renderer-Bcu3UrI-.mjs.map} +1 -1
  118. package/dist/{renderer-DxB_OfLs.mjs → renderer-BeNl_6EG.mjs} +2 -2
  119. package/dist/{renderer-DxB_OfLs.mjs.map → renderer-BeNl_6EG.mjs.map} +1 -1
  120. package/dist/{renderer-BHPgkoEL.mjs → renderer-BfZNIiSv.mjs} +2 -2
  121. package/dist/{renderer-BHPgkoEL.mjs.map → renderer-BfZNIiSv.mjs.map} +1 -1
  122. package/dist/renderer-Bgos68gc.mjs +6 -0
  123. package/dist/{renderer-CnL8Cmmr.mjs → renderer-BjDddtw9.mjs} +2 -2
  124. package/dist/renderer-BjHOvoqO.mjs +5 -0
  125. package/dist/renderer-BjXMPPlV.mjs +5 -0
  126. package/dist/{renderer-FPiLBwj5.mjs → renderer-BpVOk9ux.mjs} +2 -2
  127. package/dist/{renderer-FPiLBwj5.mjs.map → renderer-BpVOk9ux.mjs.map} +1 -1
  128. package/dist/{renderer-DJPZs_zV.mjs → renderer-Bpm145BW.mjs} +3 -3
  129. package/dist/{renderer-DJPZs_zV.mjs.map → renderer-Bpm145BW.mjs.map} +1 -1
  130. package/dist/{renderer-45ifiDkn.mjs → renderer-Brmbl12Y.mjs} +3 -3
  131. package/dist/{renderer-45ifiDkn.mjs.map → renderer-Brmbl12Y.mjs.map} +1 -1
  132. package/dist/renderer-Byobxqkv.mjs +5 -0
  133. package/dist/{renderer-DOZPZTxe.mjs → renderer-C20efMI0.mjs} +3 -3
  134. package/dist/{renderer-DOZPZTxe.mjs.map → renderer-C20efMI0.mjs.map} +1 -1
  135. package/dist/{renderer-puQhVfDS.mjs → renderer-C5AoaymD.mjs} +3 -3
  136. package/dist/{renderer-puQhVfDS.mjs.map → renderer-C5AoaymD.mjs.map} +1 -1
  137. package/dist/{renderer-B-LUGB9h.mjs → renderer-CCxk-RJR.mjs} +2 -2
  138. package/dist/{renderer-B-LUGB9h.mjs.map → renderer-CCxk-RJR.mjs.map} +1 -1
  139. package/dist/{renderer-BKi6jDi_.mjs → renderer-CKo7VR8x.mjs} +3 -3
  140. package/dist/{renderer-BKi6jDi_.mjs.map → renderer-CKo7VR8x.mjs.map} +1 -1
  141. package/dist/renderer-CKrsg3EG.mjs +5 -0
  142. package/dist/{renderer--Q0pM9Vh.mjs → renderer-COgwYqev.mjs} +3 -3
  143. package/dist/{renderer--Q0pM9Vh.mjs.map → renderer-COgwYqev.mjs.map} +1 -1
  144. package/dist/{renderer-B5b-ehRb.mjs → renderer-CP108ABB.mjs} +2 -2
  145. package/dist/{renderer-B5b-ehRb.mjs.map → renderer-CP108ABB.mjs.map} +1 -1
  146. package/dist/{renderer-BPOh9x5X.mjs → renderer-CP8wLzTU.mjs} +2 -2
  147. package/dist/{renderer-BPOh9x5X.mjs.map → renderer-CP8wLzTU.mjs.map} +1 -1
  148. package/dist/{renderer-BPiWsrC2.mjs → renderer-CT3zkfbf.mjs} +2 -2
  149. package/dist/{renderer-BPiWsrC2.mjs.map → renderer-CT3zkfbf.mjs.map} +1 -1
  150. package/dist/{renderer-CQFMyC_c.mjs → renderer-Ca1KjNCO.mjs} +2 -2
  151. package/dist/{renderer-CQFMyC_c.mjs.map → renderer-Ca1KjNCO.mjs.map} +1 -1
  152. package/dist/renderer-Cj875NuH.mjs +5 -0
  153. package/dist/{renderer-CMnlgAGs.mjs → renderer-Cm0tsbQH.mjs} +2 -2
  154. package/dist/{renderer-CMnlgAGs.mjs.map → renderer-Cm0tsbQH.mjs.map} +1 -1
  155. package/dist/renderer-CnmzJoMv.mjs +5 -0
  156. package/dist/{renderer-BtgqTg2K.mjs → renderer-Cqb7PGrI.mjs} +2 -2
  157. package/dist/{renderer-C9dqx3U7.mjs → renderer-CrxwW6VQ.mjs} +3 -3
  158. package/dist/{renderer-C9dqx3U7.mjs.map → renderer-CrxwW6VQ.mjs.map} +1 -1
  159. package/dist/renderer-CtklDaMg.mjs +5 -0
  160. package/dist/{renderer-dRXP2dJU.mjs → renderer-Cw6_NL2i.mjs} +3 -3
  161. package/dist/{renderer-dRXP2dJU.mjs.map → renderer-Cw6_NL2i.mjs.map} +1 -1
  162. package/dist/{renderer-C_1eqfI0.mjs → renderer-D-KHHWQD.mjs} +3 -3
  163. package/dist/{renderer-C_1eqfI0.mjs.map → renderer-D-KHHWQD.mjs.map} +1 -1
  164. package/dist/{renderer-CxSFvKd1.mjs → renderer-D-RhYvVl.mjs} +2 -2
  165. package/dist/{renderer-CxSFvKd1.mjs.map → renderer-D-RhYvVl.mjs.map} +1 -1
  166. package/dist/renderer-D1tK3in_.mjs +5 -0
  167. package/dist/{renderer-Nnu32rqg.mjs → renderer-D2wNFNRf.mjs} +2 -2
  168. package/dist/{renderer-Nnu32rqg.mjs.map → renderer-D2wNFNRf.mjs.map} +1 -1
  169. package/dist/renderer-D2yc9P61.mjs +5 -0
  170. package/dist/renderer-D4v-JvSr.mjs +5 -0
  171. package/dist/renderer-D7WRWDwB.mjs +5 -0
  172. package/dist/{renderer-DXFJv_e6.mjs → renderer-DA8YEo_d.mjs} +3 -3
  173. package/dist/{renderer-DXFJv_e6.mjs.map → renderer-DA8YEo_d.mjs.map} +1 -1
  174. package/dist/{renderer-CYwVNbUh.mjs → renderer-DEjo6dw_.mjs} +2 -2
  175. package/dist/{renderer-CYwVNbUh.mjs.map → renderer-DEjo6dw_.mjs.map} +1 -1
  176. package/dist/renderer-DGvA0tdB.mjs +5 -0
  177. package/dist/{renderer-Br8km655.mjs → renderer-DIEeHiQA.mjs} +2 -2
  178. package/dist/{renderer-Br8km655.mjs.map → renderer-DIEeHiQA.mjs.map} +1 -1
  179. package/dist/{renderer-SJyp9-62.mjs → renderer-DNxWOz8Q.mjs} +2 -2
  180. package/dist/{renderer-SJyp9-62.mjs.map → renderer-DNxWOz8Q.mjs.map} +1 -1
  181. package/dist/renderer-DO7v5Sdm.mjs +5 -0
  182. package/dist/renderer-DRVvt7-V.mjs +5 -0
  183. package/dist/{renderer-DGb_wgTW.mjs → renderer-DVPgWHZw.mjs} +3 -3
  184. package/dist/{renderer-DGb_wgTW.mjs.map → renderer-DVPgWHZw.mjs.map} +1 -1
  185. package/dist/{renderer-BjgFpwVK.mjs → renderer-DWq9gYRw.mjs} +3 -3
  186. package/dist/{renderer-BjgFpwVK.mjs.map → renderer-DWq9gYRw.mjs.map} +1 -1
  187. package/dist/renderer-DYMCd3jL.mjs +5 -0
  188. package/dist/{renderer-C1ctHdXl.mjs → renderer-D_j1WKMD.mjs} +3 -3
  189. package/dist/{renderer-C1ctHdXl.mjs.map → renderer-D_j1WKMD.mjs.map} +1 -1
  190. package/dist/{renderer-BsMZ5Iaq.mjs → renderer-DcAKDAnS.mjs} +3 -3
  191. package/dist/{renderer-BsMZ5Iaq.mjs.map → renderer-DcAKDAnS.mjs.map} +1 -1
  192. package/dist/{renderer-g9SstkTz.mjs → renderer-Dgj46uEU.mjs} +2 -2
  193. package/dist/{renderer-DRal7Goa.mjs → renderer-DhyMfG1v.mjs} +3 -3
  194. package/dist/{renderer-DRal7Goa.mjs.map → renderer-DhyMfG1v.mjs.map} +1 -1
  195. package/dist/{renderer-BO_wuUKA.mjs → renderer-Dj2H5JPR.mjs} +3 -3
  196. package/dist/{renderer-BO_wuUKA.mjs.map → renderer-Dj2H5JPR.mjs.map} +1 -1
  197. package/dist/{renderer-BlZgyBSo.mjs → renderer-DkOuQi84.mjs} +3 -3
  198. package/dist/{renderer-BlZgyBSo.mjs.map → renderer-DkOuQi84.mjs.map} +1 -1
  199. package/dist/renderer-DmHKw-MM.mjs +5 -0
  200. package/dist/renderer-DoDsuZ6G.mjs +5 -0
  201. package/dist/{renderer-BgjPbgbn.mjs → renderer-Dr0tXA4k.mjs} +3 -3
  202. package/dist/{renderer-BgjPbgbn.mjs.map → renderer-Dr0tXA4k.mjs.map} +1 -1
  203. package/dist/{renderer-opVuiAQR.mjs → renderer-DtMVitEF.mjs} +2 -2
  204. package/dist/{renderer-opVuiAQR.mjs.map → renderer-DtMVitEF.mjs.map} +1 -1
  205. package/dist/{renderer-D-fzEZfX.mjs → renderer-Dwwnx7Dk.mjs} +2 -2
  206. package/dist/{renderer-D-fzEZfX.mjs.map → renderer-Dwwnx7Dk.mjs.map} +1 -1
  207. package/dist/{renderer-dGEMVaZ3.mjs → renderer-DzLrvCB2.mjs} +2 -2
  208. package/dist/{renderer-dGEMVaZ3.mjs.map → renderer-DzLrvCB2.mjs.map} +1 -1
  209. package/dist/renderer-EpgclS70.mjs +5 -0
  210. package/dist/{renderer-549d94qG.mjs → renderer-FYOL5Za6.mjs} +3 -3
  211. package/dist/{renderer-549d94qG.mjs.map → renderer-FYOL5Za6.mjs.map} +1 -1
  212. package/dist/renderer-GroKiYyK.mjs +5 -0
  213. package/dist/{renderer-CciiDHuU.mjs → renderer-I0Hh9cLK.mjs} +3 -3
  214. package/dist/{renderer-CciiDHuU.mjs.map → renderer-I0Hh9cLK.mjs.map} +1 -1
  215. package/dist/renderer-MQ5K_xIS.mjs +5 -0
  216. package/dist/{renderer-C7m7Yxgc.mjs → renderer-NRMlYkX6.mjs} +3 -3
  217. package/dist/{renderer-C7m7Yxgc.mjs.map → renderer-NRMlYkX6.mjs.map} +1 -1
  218. package/dist/{renderer-BIieU9pY.mjs → renderer-Nnmi8Gt6.mjs} +3 -3
  219. package/dist/{renderer-BIieU9pY.mjs.map → renderer-Nnmi8Gt6.mjs.map} +1 -1
  220. package/dist/renderer-U43SflML.mjs +5 -0
  221. package/dist/renderer-UqOmBgCv.mjs +5 -0
  222. package/dist/{renderer-KgsVk9c6.mjs → renderer-W1bVNVR6.mjs} +3 -3
  223. package/dist/{renderer-KgsVk9c6.mjs.map → renderer-W1bVNVR6.mjs.map} +1 -1
  224. package/dist/renderer-XLULgJM0.mjs +5 -0
  225. package/dist/{renderer-CE4535aW.mjs → renderer-ZnrEfkYh.mjs} +2 -2
  226. package/dist/{renderer-CE4535aW.mjs.map → renderer-ZnrEfkYh.mjs.map} +1 -1
  227. package/dist/{renderer-RAq3LnNy.mjs → renderer-_pC-jB9O.mjs} +2 -2
  228. package/dist/{renderer-RAq3LnNy.mjs.map → renderer-_pC-jB9O.mjs.map} +1 -1
  229. package/dist/{renderer-Dz6P-rPI.mjs → renderer-iMiYStcW.mjs} +2 -2
  230. package/dist/{renderer-Dz6P-rPI.mjs.map → renderer-iMiYStcW.mjs.map} +1 -1
  231. package/dist/{renderer-S9tVGneU.mjs → renderer-jUKdkmG5.mjs} +69 -69
  232. package/dist/{renderer-S9tVGneU.mjs.map → renderer-jUKdkmG5.mjs.map} +1 -1
  233. package/dist/{renderer-_54hiCq0.mjs → renderer-k5d7JSbs.mjs} +3 -3
  234. package/dist/{renderer-_54hiCq0.mjs.map → renderer-k5d7JSbs.mjs.map} +1 -1
  235. package/dist/renderer-lNjxY1GG.mjs +5 -0
  236. package/dist/{renderer-CL-vyXgX.mjs → renderer-nHS3Sius.mjs} +3 -3
  237. package/dist/{renderer-CL-vyXgX.mjs.map → renderer-nHS3Sius.mjs.map} +1 -1
  238. package/dist/{renderer-Cp_HOmp7.mjs → renderer-ngmYIIob.mjs} +3 -3
  239. package/dist/{renderer-Cp_HOmp7.mjs.map → renderer-ngmYIIob.mjs.map} +1 -1
  240. package/dist/{renderer-LXhEPCmy.mjs → renderer-raxwsiGI.mjs} +3 -3
  241. package/dist/{renderer-LXhEPCmy.mjs.map → renderer-raxwsiGI.mjs.map} +1 -1
  242. package/dist/renderer-ukufL7pl.mjs +5 -0
  243. package/dist/renderer-wb0NCKdF.mjs +5 -0
  244. package/dist/{renderer-D8gN7aqk.mjs → renderer-zRq0j6Xy.mjs} +3 -3
  245. package/dist/{renderer-D8gN7aqk.mjs.map → renderer-zRq0j6Xy.mjs.map} +1 -1
  246. package/dist/{renderers-CP8CFWDs-BRQyvcut.mjs → renderers-CP8CFWDs-CshljORp.mjs} +2 -2
  247. package/dist/{renderers-CP8CFWDs-BRQyvcut.mjs.map → renderers-CP8CFWDs-CshljORp.mjs.map} +1 -1
  248. package/dist/sam/index.d.mts +3 -3
  249. package/dist/signals/index.d.mts +5 -5
  250. package/dist/signals/index.mjs +4 -4
  251. package/dist/{src-BzFGotRc.mjs → src-Dy4gnc6w.mjs} +43 -13
  252. package/dist/src-Dy4gnc6w.mjs.map +1 -0
  253. package/package.json +60 -60
  254. package/dist/dist-CUNvNDS3.mjs.map +0 -1
  255. package/dist/dist-D3BeAZ7X.mjs.map +0 -1
  256. package/dist/index-BFTtXosj.d.mts.map +0 -1
  257. package/dist/index-E2IXX0Cz.d.mts.map +0 -1
  258. package/dist/index-X2u96TEo.d.mts.map +0 -1
  259. package/dist/index-a6JrbAK0.d.mts.map +0 -1
  260. package/dist/preview-engine/chunk-77WWW75O.mjs +0 -5
  261. package/dist/preview-engine/chunk-7M5LE6EM.mjs +0 -483
  262. package/dist/preview-engine/chunk-FRJPXWAG.mjs +0 -1
  263. package/dist/preview-engine/renderers-TTXTV4PC.mjs +0 -228
  264. package/dist/program-Bn6ko_I4.mjs.map +0 -1
  265. package/dist/renderer-1aSF5BmY.mjs +0 -5
  266. package/dist/renderer-B2e4Gr9u.mjs +0 -5
  267. package/dist/renderer-B6fz4j50.mjs +0 -5
  268. package/dist/renderer-BEftIYw-.mjs +0 -5
  269. package/dist/renderer-BHTnO-7e.mjs +0 -5
  270. package/dist/renderer-BMlnQqTO.mjs +0 -5
  271. package/dist/renderer-BOzcFezY.mjs +0 -5
  272. package/dist/renderer-BVbjJIKJ.mjs +0 -5
  273. package/dist/renderer-BXsHKf4k.mjs +0 -5
  274. package/dist/renderer-Bks49NVl.mjs +0 -5
  275. package/dist/renderer-C-t7qFKr.mjs +0 -5
  276. package/dist/renderer-C24n-6CX.mjs +0 -6
  277. package/dist/renderer-CCReX-7M.mjs +0 -5
  278. package/dist/renderer-CIxFQrAm.mjs +0 -5
  279. package/dist/renderer-CLHBMIqJ.mjs +0 -5
  280. package/dist/renderer-CMJKi-Ye.mjs +0 -5
  281. package/dist/renderer-C_H3mHZa.mjs +0 -5
  282. package/dist/renderer-CfkTdR5B.mjs +0 -5
  283. package/dist/renderer-CnhkXJ0R.mjs +0 -5
  284. package/dist/renderer-CqpyVsq_.mjs +0 -5
  285. package/dist/renderer-CtWgIpTE.mjs +0 -5
  286. package/dist/renderer-CyuKr5ca.mjs +0 -5
  287. package/dist/renderer-D9ON_1Hf.mjs +0 -5
  288. package/dist/renderer-DAnTgh4o.mjs +0 -5
  289. package/dist/renderer-DWgpivA3.mjs +0 -5
  290. package/dist/renderer-Dh_qNNwR.mjs +0 -5
  291. package/dist/renderer-DkCpXPMP.mjs +0 -5
  292. package/dist/renderer-Dre-6cDR.mjs +0 -5
  293. package/dist/renderer-Ds5z4_HX.mjs +0 -5
  294. package/dist/renderer-HGSCOMyP.mjs +0 -5
  295. package/dist/renderer-KMY8W-cJ.mjs +0 -5
  296. package/dist/renderer-R21YvXz3.mjs +0 -5
  297. package/dist/renderer-RtzCz37W.mjs +0 -5
  298. package/dist/renderer-iMrqAjix.mjs +0 -5
  299. package/dist/renderer-sMed1u1O.mjs +0 -5
  300. package/dist/renderer-tsAuo46l.mjs +0 -5
  301. package/dist/renderer-vll8LX0I.mjs.map +0 -1
  302. package/dist/src-BzFGotRc.mjs.map +0 -1
@@ -1537,15 +1537,15 @@ var SignalRegistry = class {
1537
1537
  res.lastProcessedFrame = cacheFrame;
1538
1538
  res.lastFingerprint = fingerprint;
1539
1539
  if (isSamplesArray(rawSamples) && rawSamples.length > 0) {
1540
- const flat = new Float32Array(numSamples);
1540
+ const flat$1 = new Float32Array(numSamples);
1541
1541
  const total = rawSamples.length;
1542
1542
  const srcFps = Number(normalizedSd?.fps) || 24;
1543
1543
  for (let i = 0; i < numSamples; i++) {
1544
1544
  const t = elapsedTime + i / sampleRate;
1545
1545
  const sIdx = Math.min(total - 1, Math.max(0, Math.round(t * srcFps)));
1546
- flat[i] = Number(rawSamples[sIdx]) || 0;
1546
+ flat$1[i] = Number(rawSamples[sIdx]) || 0;
1547
1547
  }
1548
- device.queue.writeBuffer(res.buffer, 0, flat);
1548
+ device.queue.writeBuffer(res.buffer, 0, flat$1);
1549
1549
  } else if (normalizedSd?.type === "generator" && (normalizedSd.customWGSL || normalizedSd.fnBody)) {
1550
1550
  const signalEncoder = device.createCommandEncoder({ label: `signal_1d_encoder_${nodeId}` });
1551
1551
  this.dispatch1DGenerator(device, signalEncoder, res, elapsedTime, duration$2, normalizedSd, numSamples, sampleRate, frame, fps);
@@ -1560,8 +1560,8 @@ var SignalRegistry = class {
1560
1560
  device.queue.submit([signalEncoder.finish()]);
1561
1561
  } else {
1562
1562
  const val = Number(normalizedSd?.offset ?? 0);
1563
- const flat = new Float32Array(numSamples).fill(val);
1564
- device.queue.writeBuffer(res.buffer, 0, flat);
1563
+ const flat$1 = new Float32Array(numSamples).fill(val);
1564
+ device.queue.writeBuffer(res.buffer, 0, flat$1);
1565
1565
  }
1566
1566
  } else if (normalizedSd?.type === "signal_math") {
1567
1567
  const sigAId = normalizedSd.signalA?.nodeId ?? `${nodeId}_a`;
@@ -1582,8 +1582,8 @@ var SignalRegistry = class {
1582
1582
  device.queue.submit([signalEncoder.finish()]);
1583
1583
  } else {
1584
1584
  const val = Number(normalizedSd?.offset ?? 0);
1585
- const flat = new Float32Array(numSamples).fill(val);
1586
- device.queue.writeBuffer(res.buffer, 0, flat);
1585
+ const flat$1 = new Float32Array(numSamples).fill(val);
1586
+ device.queue.writeBuffer(res.buffer, 0, flat$1);
1587
1587
  }
1588
1588
  return res.buffer;
1589
1589
  }
@@ -1649,7 +1649,7 @@ var SignalRegistry = class {
1649
1649
  device.queue.submit([signalEncoder.finish()]);
1650
1650
  return res.textureView;
1651
1651
  } else if (isSamplesArray(rawSamples) && rawSamples.length > 0) {
1652
- const flat = new Float32Array(targetWidth * targetHeight * 4);
1652
+ const flat$1 = new Float32Array(targetWidth * targetHeight * 4);
1653
1653
  const total = rawSamples.length;
1654
1654
  const baseTime = frame !== void 0 && fps !== void 0 && fps > 0 ? frame / fps : elapsedTime;
1655
1655
  for (let i = 0; i < targetWidth; i++) {
@@ -1659,13 +1659,13 @@ var SignalRegistry = class {
1659
1659
  const val = Number(rawSamples[sIdx]) || 0;
1660
1660
  for (let row = 0; row < targetHeight; row++) {
1661
1661
  const idx = (row * targetWidth + i) * 4;
1662
- flat[idx] = val;
1663
- flat[idx + 1] = val;
1664
- flat[idx + 2] = val;
1665
- flat[idx + 3] = val;
1662
+ flat$1[idx] = val;
1663
+ flat$1[idx + 1] = val;
1664
+ flat$1[idx + 2] = val;
1665
+ flat$1[idx + 3] = val;
1666
1666
  }
1667
1667
  }
1668
- const halfFlat = encodeFloat32ToFloat16(flat);
1668
+ const halfFlat = encodeFloat32ToFloat16(flat$1);
1669
1669
  device.queue.writeTexture({ texture: res.texture }, halfFlat, {
1670
1670
  bytesPerRow: targetWidth * 8,
1671
1671
  rowsPerImage: targetHeight
@@ -1691,14 +1691,14 @@ var SignalRegistry = class {
1691
1691
  device.queue.submit([signalEncoder.finish()]);
1692
1692
  } else {
1693
1693
  const val = Number(normalizedSd?.offset ?? 0);
1694
- const flat = new Float32Array(targetWidth * targetHeight * 4);
1694
+ const flat$1 = new Float32Array(targetWidth * targetHeight * 4);
1695
1695
  for (let i = 0; i < targetWidth * targetHeight; i++) {
1696
- flat[i * 4] = val;
1697
- flat[i * 4 + 1] = val;
1698
- flat[i * 4 + 2] = val;
1699
- flat[i * 4 + 3] = 1;
1696
+ flat$1[i * 4] = val;
1697
+ flat$1[i * 4 + 1] = val;
1698
+ flat$1[i * 4 + 2] = val;
1699
+ flat$1[i * 4 + 3] = 1;
1700
1700
  }
1701
- const halfFlat = encodeFloat32ToFloat16(flat);
1701
+ const halfFlat = encodeFloat32ToFloat16(flat$1);
1702
1702
  device.queue.writeTexture({ texture: res.texture }, halfFlat, {
1703
1703
  bytesPerRow: targetWidth * 8,
1704
1704
  rowsPerImage: targetHeight
@@ -16151,9 +16151,9 @@ var ShaderStore = class {
16151
16151
  getCacheKey(nodeId, renderId) {
16152
16152
  return `${renderId ?? "global"}:${nodeId}`;
16153
16153
  }
16154
- register(nodeId, shader, renderId) {
16155
- this.store.set(this.getCacheKey(nodeId, renderId), shader);
16156
- if (renderId) this.store.set(this.getCacheKey(nodeId, void 0), shader);
16154
+ register(nodeId, shader$1, renderId) {
16155
+ this.store.set(this.getCacheKey(nodeId, renderId), shader$1);
16156
+ if (renderId) this.store.set(this.getCacheKey(nodeId, void 0), shader$1);
16157
16157
  }
16158
16158
  get(nodeId, renderId) {
16159
16159
  const res = this.store.get(this.getCacheKey(nodeId, renderId));
@@ -24530,9 +24530,11 @@ function drawParagraphNode(ctx, pass, props) {
24530
24530
  scale = effect.scale;
24531
24531
  if (blurOverride === void 0) blurAmount = effect.blurAmount;
24532
24532
  }
24533
+ const isHighlighted = props.highlightWordIndex !== void 0 && glyph.wordIndex === props.highlightWordIndex;
24534
+ const spanColor = glyph.fill ? parseColor(glyph.fill) : void 0;
24535
+ let fillColor = isHighlighted ? highlightColor : spanColor ?? baseColor;
24533
24536
  let customScaleX = 1;
24534
24537
  let customScaleY = 1;
24535
- let animatorColor;
24536
24538
  let transZ = 0;
24537
24539
  let chromaticShift = 0;
24538
24540
  let dissolveProgress = 0;
@@ -24551,7 +24553,7 @@ function drawParagraphNode(ctx, pass, props) {
24551
24553
  scaleY: customScaleY,
24552
24554
  opacity,
24553
24555
  blurAmount,
24554
- color: baseColor
24556
+ color: fillColor
24555
24557
  }, false, props.frame ?? 0, props.fps ?? 24, props.signals);
24556
24558
  transX = kResult.transX;
24557
24559
  transY = kResult.transY;
@@ -24561,7 +24563,7 @@ function drawParagraphNode(ctx, pass, props) {
24561
24563
  customScaleY = kResult.scaleY;
24562
24564
  opacity = kResult.opacity;
24563
24565
  if (blurOverride === void 0) blurAmount = kResult.blurAmount;
24564
- if (kResult.color) animatorColor = kResult.color;
24566
+ if (kResult.color) fillColor = kResult.color;
24565
24567
  if (kResult.lineSpacing !== void 0) transY += computeLineLeadingDisplacement(glyph.lineIndex, layout.linesCount, kResult.lineSpacing, kResult.lineAnchor ?? 0);
24566
24568
  transZ = kResult.transZ ?? 0;
24567
24569
  chromaticShift = kResult.chromaticShift ?? 0;
@@ -24579,9 +24581,7 @@ function drawParagraphNode(ctx, pass, props) {
24579
24581
  const contentWidth = layout.glyphs.length > 0 ? layout.glyphs[layout.glyphs.length - 1].x + fontSize : props.dstRect.width;
24580
24582
  glyphX = evaluateMarqueeOffset(glyph.x, props.dstRect.width, contentWidth, props.marquee, elapsedSec);
24581
24583
  }
24582
- const isHighlighted = props.highlightWordIndex !== void 0 && glyph.wordIndex === props.highlightWordIndex;
24583
- const spanColor = glyph.fill ? parseColor(glyph.fill) : void 0;
24584
- const col = animatorColor ?? colorOverride ?? (isHighlighted ? highlightColor : spanColor ?? baseColor);
24584
+ const col = colorOverride ?? fillColor;
24585
24585
  const writeInstance = (boldShift) => {
24586
24586
  const offset = visibleCount * 24;
24587
24587
  const fontScale = glyph.fontScale ?? fontSize / targetFont.unitsPerEm;
@@ -27990,6 +27990,118 @@ var ClipStack = class {
27990
27990
  }
27991
27991
  };
27992
27992
  /**
27993
+ * Filling compound paths: every subpath is a contour, contours nest, and a
27994
+ * region is filled by SVG's nonzero rule (the windings of the contours
27995
+ * around it, summed, are not zero). Each filled contour is triangulated with
27996
+ * its direct children as holes, so a map's coastlines and islands, a
27997
+ * letter's counter or a ring fill as drawn instead of being bridged into
27998
+ * one polygon.
27999
+ *
28000
+ * Contours are taken not to cross each other (each pair is nested or
28001
+ * apart), which holds for maps, glyphs and icons; a contour that crosses
28002
+ * itself is still triangulated as well as earcut can.
28003
+ */
28004
+ function signedArea(points) {
28005
+ let area = 0;
28006
+ for (let i = 0; i < points.length; i++) {
28007
+ const a = points[i];
28008
+ const b = points[(i + 1) % points.length];
28009
+ area += a.x * b.y - b.x * a.y;
28010
+ }
28011
+ return area / 2;
28012
+ }
28013
+ function flat(points) {
28014
+ const out = [];
28015
+ for (const p of points) out.push(p.x, p.y);
28016
+ return out;
28017
+ }
28018
+ function boxOf(points) {
28019
+ const box = {
28020
+ x0: Infinity,
28021
+ y0: Infinity,
28022
+ x1: -Infinity,
28023
+ y1: -Infinity
28024
+ };
28025
+ for (const p of points) {
28026
+ box.x0 = Math.min(box.x0, p.x);
28027
+ box.y0 = Math.min(box.y0, p.y);
28028
+ box.x1 = Math.max(box.x1, p.x);
28029
+ box.y1 = Math.max(box.y1, p.y);
28030
+ }
28031
+ return box;
28032
+ }
28033
+ /** The centroid of the contour's largest triangle: inside it, away from its edges. */
28034
+ function insidePoint(points) {
28035
+ const coords = flat(points);
28036
+ const tris = earcut(coords);
28037
+ const at = (k) => ({
28038
+ x: coords[k * 2],
28039
+ y: coords[k * 2 + 1]
28040
+ });
28041
+ let best = -1;
28042
+ let inside = points[0];
28043
+ for (let i = 0; i < tris.length; i += 3) {
28044
+ const a = at(tris[i]);
28045
+ const b = at(tris[i + 1]);
28046
+ const c = at(tris[i + 2]);
28047
+ const size = Math.abs((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y));
28048
+ if (size > best) {
28049
+ best = size;
28050
+ inside = {
28051
+ x: (a.x + b.x + c.x) / 3,
28052
+ y: (a.y + b.y + c.y) / 3
28053
+ };
28054
+ }
28055
+ }
28056
+ return inside;
28057
+ }
28058
+ function contains(points, p) {
28059
+ let inside = false;
28060
+ for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
28061
+ const a = points[i];
28062
+ const b = points[j];
28063
+ if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside = !inside;
28064
+ }
28065
+ return inside;
28066
+ }
28067
+ const encloses = (p, c) => p.box.x0 <= c.box.x0 && p.box.y0 <= c.box.y0 && p.box.x1 >= c.box.x1 && p.box.y1 >= c.box.y1 && contains(p.points, c.inside);
28068
+ /** Triangles filling closed `contours` by the nonzero rule, as flat [x, y, …] vertices. */
28069
+ function fillContours(contours) {
28070
+ const all = contours.map((points) => ({
28071
+ points,
28072
+ area: signedArea(points),
28073
+ box: boxOf(points),
28074
+ inside: insidePoint(points),
28075
+ parent: -1,
28076
+ winding: 0
28077
+ }));
28078
+ const order = all.map((_, i) => i).sort((a, b) => Math.abs(all[b].area) - Math.abs(all[a].area));
28079
+ order.forEach((ci, rank) => {
28080
+ const c = all[ci];
28081
+ for (let r = rank - 1; r >= 0; r--) {
28082
+ const pi = order[r];
28083
+ if (encloses(all[pi], c)) {
28084
+ c.parent = pi;
28085
+ break;
28086
+ }
28087
+ }
28088
+ c.winding = (c.parent >= 0 ? all[c.parent].winding : 0) + Math.sign(c.area);
28089
+ });
28090
+ const out = [];
28091
+ all.forEach((c, ci) => {
28092
+ if (c.winding === 0) return;
28093
+ const coords = flat(c.points);
28094
+ const holeStarts = [];
28095
+ for (const h of all) {
28096
+ if (h.parent !== ci) continue;
28097
+ holeStarts.push(coords.length / 2);
28098
+ for (const p of h.points) coords.push(p.x, p.y);
28099
+ }
28100
+ for (const k of earcut(coords, holeStarts)) out.push(coords[k * 2], coords[k * 2 + 1]);
28101
+ });
28102
+ return new Float32Array(out);
28103
+ }
28104
+ /**
27993
28105
  * Sample a cubic bezier curve into linear segments.
27994
28106
  */
27995
28107
  function sampleCubicBezier(p0, p1, p2, p3, steps = 24) {
@@ -28097,8 +28209,26 @@ function sampleSvgArc(p0, rxIn, ryIn, rotationDeg, largeArc, sweep, p1) {
28097
28209
  * Parse an SVG path string (M, L, H, V, C, S, Q, T, A, Z) into line segments.
28098
28210
  */
28099
28211
  function parseSvgPathToSegments(d) {
28212
+ return parseSvgPath(d).segments;
28213
+ }
28214
+ /**
28215
+ * The path's segments, and for each the subpath it belongs to (every M
28216
+ * starts one): fills need to know where each subpath begins.
28217
+ */
28218
+ function parseSvgPath(d) {
28100
28219
  const segments = [];
28101
- if (!d || typeof d !== "string") return segments;
28220
+ const subpaths = [];
28221
+ if (!d || typeof d !== "string") return {
28222
+ segments,
28223
+ subpaths
28224
+ };
28225
+ let subpath = -1;
28226
+ const emit = (...added) => {
28227
+ for (const seg of added) {
28228
+ segments.push(seg);
28229
+ subpaths.push(subpath);
28230
+ }
28231
+ };
28102
28232
  const tokens = [];
28103
28233
  const regex = /([a-df-z])|([-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?)/gi;
28104
28234
  let match;
@@ -28144,9 +28274,10 @@ function parseSvgPathToSegments(d) {
28144
28274
  x,
28145
28275
  y
28146
28276
  };
28277
+ subpath++;
28147
28278
  first = false;
28148
28279
  } else {
28149
- segments.push({
28280
+ emit({
28150
28281
  p0: current,
28151
28282
  p1: {
28152
28283
  x,
@@ -28171,7 +28302,7 @@ function parseSvgPathToSegments(d) {
28171
28302
  x += current.x;
28172
28303
  y += current.y;
28173
28304
  }
28174
- segments.push({
28305
+ emit({
28175
28306
  p0: current,
28176
28307
  p1: {
28177
28308
  x,
@@ -28194,7 +28325,7 @@ function parseSvgPathToSegments(d) {
28194
28325
  x,
28195
28326
  y: current.y
28196
28327
  };
28197
- segments.push({
28328
+ emit({
28198
28329
  p0: current,
28199
28330
  p1: next
28200
28331
  });
@@ -28211,7 +28342,7 @@ function parseSvgPathToSegments(d) {
28211
28342
  x: current.x,
28212
28343
  y
28213
28344
  };
28214
- segments.push({
28345
+ emit({
28215
28346
  p0: current,
28216
28347
  p1: next
28217
28348
  });
@@ -28248,7 +28379,7 @@ function parseSvgPathToSegments(d) {
28248
28379
  x,
28249
28380
  y
28250
28381
  };
28251
- segments.push(...sampleCubicBezier(current, p1, p2, end));
28382
+ emit(...sampleCubicBezier(current, p1, p2, end));
28252
28383
  current = end;
28253
28384
  lastCubicControl = p2;
28254
28385
  }
@@ -28283,7 +28414,7 @@ function parseSvgPathToSegments(d) {
28283
28414
  x,
28284
28415
  y
28285
28416
  };
28286
- segments.push(...sampleCubicBezier(current, p1, p2, end));
28417
+ emit(...sampleCubicBezier(current, p1, p2, end));
28287
28418
  current = end;
28288
28419
  lastCubicControl = p2;
28289
28420
  }
@@ -28309,7 +28440,7 @@ function parseSvgPathToSegments(d) {
28309
28440
  x,
28310
28441
  y
28311
28442
  };
28312
- segments.push(...sampleQuadraticBezier(current, p1, end));
28443
+ emit(...sampleQuadraticBezier(current, p1, end));
28313
28444
  current = end;
28314
28445
  lastQuadControl = p1;
28315
28446
  }
@@ -28336,7 +28467,7 @@ function parseSvgPathToSegments(d) {
28336
28467
  x,
28337
28468
  y
28338
28469
  };
28339
- segments.push(...sampleQuadraticBezier(current, p1, end));
28470
+ emit(...sampleQuadraticBezier(current, p1, end));
28340
28471
  current = end;
28341
28472
  lastQuadControl = p1;
28342
28473
  }
@@ -28359,14 +28490,14 @@ function parseSvgPathToSegments(d) {
28359
28490
  x,
28360
28491
  y
28361
28492
  };
28362
- segments.push(...sampleSvgArc(current, rx, ry, rotation, largeArc, sweep, end));
28493
+ emit(...sampleSvgArc(current, rx, ry, rotation, largeArc, sweep, end));
28363
28494
  current = end;
28364
28495
  }
28365
28496
  lastCubicControl = null;
28366
28497
  lastQuadControl = null;
28367
28498
  break;
28368
28499
  case "Z":
28369
- if (Math.hypot(current.x - subpathStart.x, current.y - subpathStart.y) > 1e-5) segments.push({
28500
+ if (Math.hypot(current.x - subpathStart.x, current.y - subpathStart.y) > 1e-5) emit({
28370
28501
  p0: current,
28371
28502
  p1: subpathStart
28372
28503
  });
@@ -28377,7 +28508,10 @@ function parseSvgPathToSegments(d) {
28377
28508
  default: break;
28378
28509
  }
28379
28510
  }
28380
- return segments;
28511
+ return {
28512
+ segments,
28513
+ subpaths
28514
+ };
28381
28515
  }
28382
28516
  /**
28383
28517
  * Trim linear path segments using arc-length parameterization.
@@ -28438,6 +28572,19 @@ function trimPathSegments(segments, trimStart, trimEnd, trimOffset = 0) {
28438
28572
  }
28439
28573
  }
28440
28574
  /**
28575
+ * A dash pattern as SVG's `stroke-dasharray` writes it ("16 12", "4,8") or as
28576
+ * numbers, ready for {@link dashPathSegments}. Following SVG, an odd list is
28577
+ * repeated to make it even, and a negative or unreadable entry, or a pattern
28578
+ * of zero length, means a solid stroke (`undefined`).
28579
+ */
28580
+ function parseDashArray(value) {
28581
+ if (value === void 0) return void 0;
28582
+ const parts = typeof value === "string" ? value.trim().split(/[\s,]+/).filter(Boolean).map(Number) : [...value];
28583
+ if (parts.length === 0 || parts.some((n) => !Number.isFinite(n) || n < 0)) return void 0;
28584
+ if (parts.reduce((sum, n) => sum + n, 0) <= 0) return void 0;
28585
+ return parts.length % 2 === 1 ? [...parts, ...parts] : parts;
28586
+ }
28587
+ /**
28441
28588
  * Apply dash patterns along linear path segments.
28442
28589
  */
28443
28590
  function dashPathSegments(segments, dashArray, dashOffset = 0) {
@@ -28649,6 +28796,44 @@ function triangulatePolygon(contour) {
28649
28796
  if (rem.length === 3) triangles.push(rem[0], rem[1], rem[2]);
28650
28797
  return triangles;
28651
28798
  }
28799
+ /** A path's subpaths as closed point lists, degenerate ones dropped. */
28800
+ function pathContours(d) {
28801
+ const bySubpath = /* @__PURE__ */ new Map();
28802
+ const { segments, subpaths } = parseSvgPath(d);
28803
+ segments.forEach((seg, i) => {
28804
+ const id = subpaths[i] ?? 0;
28805
+ let points = bySubpath.get(id);
28806
+ if (!points) {
28807
+ points = [seg.p0];
28808
+ bySubpath.set(id, points);
28809
+ }
28810
+ points.push(seg.p1);
28811
+ });
28812
+ const contours = [];
28813
+ for (const points of bySubpath.values()) {
28814
+ const first = points[0];
28815
+ const last = points[points.length - 1];
28816
+ if (Math.hypot(last.x - first.x, last.y - first.y) < 1e-5) points.pop();
28817
+ if (points.length >= 3 && Math.abs(signedArea(points)) > 1e-6) contours.push(points);
28818
+ }
28819
+ return contours;
28820
+ }
28821
+ /** Path fills are redrawn every frame from the same data: keep the last few. */
28822
+ const PATH_FILL_CACHE = 64;
28823
+ const pathFills = /* @__PURE__ */ new Map();
28824
+ /** Triangles filling path `d` by the nonzero rule (see {@link fillContours}). */
28825
+ function cachedPathFill(d) {
28826
+ const hit = pathFills.get(d);
28827
+ if (hit) {
28828
+ pathFills.delete(d);
28829
+ pathFills.set(d, hit);
28830
+ return hit;
28831
+ }
28832
+ const tris = fillContours(pathContours(d));
28833
+ pathFills.set(d, tris);
28834
+ if (pathFills.size > PATH_FILL_CACHE) pathFills.delete(pathFills.keys().next().value);
28835
+ return tris;
28836
+ }
28652
28837
  /**
28653
28838
  * Triangulate a shape geometry into a flat Float32Array of 2D vertex pairs [x, y, x, y, ...].
28654
28839
  */
@@ -28773,19 +28958,16 @@ function triangulateShape(config$1) {
28773
28958
  yBotHead
28774
28959
  ]);
28775
28960
  }
28776
- if (shapeType === "path" && config$1.path) {
28777
- const segments = parseSvgPathToSegments(config$1.path);
28778
- if (segments.length === 0) return new Float32Array(0);
28779
- const tris = triangulatePolygon(segments.map((s) => s.p0));
28780
- const out = new Float32Array(tris.length * 2);
28781
- for (let i = 0; i < tris.length; i++) {
28782
- out[i * 2] = tris[i].x;
28783
- out[i * 2 + 1] = tris[i].y;
28784
- }
28785
- return out;
28786
- }
28961
+ if (shapeType === "path" && config$1.path) return cachedPathFill(config$1.path);
28787
28962
  return new Float32Array(0);
28788
28963
  }
28964
+ /**
28965
+ * Strokes a path in one instanced draw: each instance is one straight
28966
+ * segment (p0, p1), expanded by the vertex shader into a quad that covers it
28967
+ * plus half the stroke width and 2 px of antialiasing; the fragment shader
28968
+ * shades the segment's capped distance field. Everything shared by the
28969
+ * path (transform, colour, width, cap) is in the uniforms.
28970
+ */
28789
28971
  const pathWgsl = `
28790
28972
  struct Uniforms {
28791
28973
  transformCol0 : vec4<f32>,
@@ -28794,8 +28976,6 @@ struct Uniforms {
28794
28976
  params : vec4<f32>, // x = opacity, y = surfaceWidth, z = surfaceHeight, w = strokeWidth
28795
28977
  color : vec4<f32>,
28796
28978
  style : vec4<f32>, // x = capType (0 = round, 1 = butt, 2 = square)
28797
- p0 : vec2<f32>,
28798
- p1 : vec2<f32>,
28799
28979
  };
28800
28980
 
28801
28981
  @group(0) @binding(0) var<uniform> u : Uniforms;
@@ -28803,17 +28983,30 @@ struct Uniforms {
28803
28983
  struct VSOut {
28804
28984
  @builtin(position) pos : vec4<f32>,
28805
28985
  @location(0) localPos : vec2<f32>,
28986
+ @location(1) @interpolate(flat) p0 : vec2<f32>,
28987
+ @location(2) @interpolate(flat) p1 : vec2<f32>,
28806
28988
  };
28807
28989
 
28808
- @vertex fn vs(@location(0) xy : vec2<f32>) -> VSOut {
28990
+ @vertex fn vs(@builtin(vertex_index) corner : u32, @location(0) seg : vec4<f32>) -> VSOut {
28991
+ let p0 = seg.xy;
28992
+ let p1 = seg.zw;
28993
+ let d = p1 - p0;
28994
+ let len = max(length(d), 1e-6);
28995
+ let padding = u.params.w * 0.5 + 2.0;
28996
+ let along = (d / len) * padding;
28997
+ let across = vec2<f32>(-along.y, along.x);
28998
+ // Triangle strip: p0 −along ∓across, then p1 +along ∓across.
28999
+ let atEnd = corner >= 2u;
29000
+ let side = select(-1.0, 1.0, (corner & 1u) == 1u);
29001
+ let xy = select(p0 - along, p1 + along, atEnd) + across * side;
29002
+
28809
29003
  let transform = mat3x3<f32>(u.transformCol0.xyz, u.transformCol1.xyz, u.transformCol2.xyz);
28810
29004
  let p = transform * vec3<f32>(xy, 1.0);
28811
-
28812
29005
  let surfaceSize = u.params.yz;
28813
29006
  let clipX = (p.x / surfaceSize.x) * 2.0 - 1.0;
28814
29007
  let clipY = 1.0 - (p.y / surfaceSize.y) * 2.0;
28815
29008
 
28816
- return VSOut(vec4<f32>(clipX, clipY, 0.0, 1.0), xy);
29009
+ return VSOut(vec4<f32>(clipX, clipY, 0.0, 1.0), xy, p0, p1);
28817
29010
  }
28818
29011
 
28819
29012
  fn sdSegmentWithCap(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>, radius: f32, capType: f32) -> f32 {
@@ -28825,10 +29018,10 @@ fn sdSegmentWithCap(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>, radius: f32, capTy
28825
29018
  let dir = ba / len;
28826
29019
  let normal = vec2<f32>(-dir.y, dir.x);
28827
29020
  let pa = p - a;
28828
-
29021
+
28829
29022
  let proj = dot(pa, dir);
28830
29023
  let perp = abs(dot(pa, normal));
28831
-
29024
+
28832
29025
  if (capType > 0.5 && capType < 1.5) {
28833
29026
  // Butt cap: terminates strictly at endpoints a and b
28834
29027
  let dx = max(-proj, proj - len);
@@ -28852,15 +29045,15 @@ fn sdSegmentWithCap(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>, radius: f32, capTy
28852
29045
 
28853
29046
  @fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
28854
29047
  let radius = u.params.w * 0.5;
28855
- let d = sdSegmentWithCap(in.localPos, u.p0, u.p1, radius, u.style.x);
28856
-
29048
+ let d = sdSegmentWithCap(in.localPos, in.p0, in.p1, radius, u.style.x);
29049
+
28857
29050
  let fw = max(fwidth(d), 1e-5);
28858
29051
  let edgeAlpha = clamp(0.5 - d / fw, 0.0, 1.0);
28859
29052
  let alpha = u.color.a * u.params.x * edgeAlpha;
28860
29053
  if (alpha <= 0.0) {
28861
29054
  discard;
28862
29055
  }
28863
-
29056
+
28864
29057
  return vec4<f32>(u.color.rgb * alpha, alpha);
28865
29058
  }
28866
29059
  `;
@@ -28917,127 +29110,111 @@ function createVec2Pipeline(device, options) {
28917
29110
  primitive: { topology: options.topology }
28918
29111
  });
28919
29112
  }
29113
+ /** A straight segment's two ends, the instance the stroke shader expands into a quad. */
29114
+ const SEGMENT_FLOATS = 4;
28920
29115
  var PathPipeline = class {
28921
29116
  device;
28922
29117
  pipeline;
28923
29118
  maxPipeline;
28924
29119
  uniformLayout;
28925
29120
  uniformPool;
28926
- vertexPool;
29121
+ segmentPool;
28927
29122
  uniformBindGroupCache = new UniformBindGroupCache();
28928
- scratchData = new Float32Array(28);
29123
+ scratchData = new Float32Array(24);
28929
29124
  constructor(device, format) {
28930
29125
  this.device = device;
28931
29126
  this.uniformPool = new BufferPool(4096, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
28932
- this.vertexPool = new BufferPool(4096, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST);
29127
+ this.segmentPool = new BufferPool(4096, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST);
28933
29128
  const module$1 = device.createShaderModule({
28934
29129
  label: "path.wgsl",
28935
29130
  code: pathWgsl
28936
29131
  });
28937
29132
  this.uniformLayout = createUniformLayout(device);
28938
- this.pipeline = createVec2Pipeline(device, {
28939
- label: "PathPipeline",
28940
- module: module$1,
28941
- uniformLayout: this.uniformLayout,
28942
- format,
28943
- topology: "triangle-strip"
29133
+ const create = (label, blend) => device.createRenderPipeline({
29134
+ label,
29135
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.uniformLayout] }),
29136
+ vertex: {
29137
+ module: module$1,
29138
+ entryPoint: "vs",
29139
+ buffers: [{
29140
+ arrayStride: SEGMENT_FLOATS * 4,
29141
+ stepMode: "instance",
29142
+ attributes: [{
29143
+ shaderLocation: 0,
29144
+ offset: 0,
29145
+ format: "float32x4"
29146
+ }]
29147
+ }]
29148
+ },
29149
+ fragment: {
29150
+ module: module$1,
29151
+ entryPoint: "fs",
29152
+ targets: [{
29153
+ format,
29154
+ blend
29155
+ }]
29156
+ },
29157
+ primitive: { topology: "triangle-strip" }
28944
29158
  });
28945
- this.maxPipeline = createVec2Pipeline(device, {
28946
- label: "PathPipeline.max",
28947
- module: module$1,
28948
- uniformLayout: this.uniformLayout,
28949
- format,
28950
- topology: "triangle-strip",
28951
- blend: {
28952
- color: {
28953
- srcFactor: "one",
28954
- dstFactor: "one",
28955
- operation: "max"
28956
- },
28957
- alpha: {
28958
- srcFactor: "one",
28959
- dstFactor: "one",
28960
- operation: "max"
28961
- }
29159
+ this.pipeline = create("PathPipeline", PREMULTIPLIED_OVER);
29160
+ this.maxPipeline = create("PathPipeline.max", {
29161
+ color: {
29162
+ srcFactor: "one",
29163
+ dstFactor: "one",
29164
+ operation: "max"
29165
+ },
29166
+ alpha: {
29167
+ srcFactor: "one",
29168
+ dstFactor: "one",
29169
+ operation: "max"
28962
29170
  }
28963
29171
  });
28964
29172
  }
28965
29173
  resetPools() {
28966
29174
  this.uniformPool.reset();
28967
- this.vertexPool.reset();
29175
+ this.segmentPool.reset();
28968
29176
  }
28969
29177
  drawPath(pass, transformStack, pathStr, color, strokeWidth, surfaceWidth, surfaceHeight, opts) {
28970
- let segments = parseSvgPathToSegments(pathStr);
28971
- if (segments.length === 0) return;
28972
- if (opts?.trimStart !== void 0 || opts?.trimEnd !== void 0 || opts?.trimOffset !== void 0) segments = trimPathSegments(segments, opts.trimStart ?? 0, opts.trimEnd ?? 1, opts.trimOffset ?? 0);
28973
- if (opts?.strokeDashArray && opts.strokeDashArray.length > 0) segments = dashPathSegments(segments, opts.strokeDashArray, opts.strokeDashOffset ?? 0);
28974
- if (segments.length === 0) return;
28975
- const c = parseColor(color);
28976
- const opacity = opts?.opacity ?? 1;
28977
- let capType = 0;
28978
- if (opts?.strokeLineCap === "butt") capType = 1;
28979
- else if (opts?.strokeLineCap === "square") capType = 2;
28980
- for (const segment of segments) this.drawSegment(pass, transformStack, segment.p0, segment.p1, c, strokeWidth, capType, surfaceWidth, surfaceHeight, opacity, opts);
29178
+ this.drawSegments(pass, transformStack, parseSvgPathToSegments(pathStr), color, strokeWidth, surfaceWidth, surfaceHeight, opts);
28981
29179
  }
28982
29180
  drawSegments(pass, transformStack, segments, color, strokeWidth, surfaceWidth, surfaceHeight, opts) {
28983
29181
  let activeSegments = segments;
28984
29182
  if (opts?.trimStart !== void 0 || opts?.trimEnd !== void 0 || opts?.trimOffset !== void 0) activeSegments = trimPathSegments(activeSegments, opts.trimStart ?? 0, opts.trimEnd ?? 1, opts.trimOffset ?? 0);
28985
29183
  if (opts?.strokeDashArray && opts.strokeDashArray.length > 0) activeSegments = dashPathSegments(activeSegments, opts.strokeDashArray, opts.strokeDashOffset ?? 0);
28986
- if (activeSegments.length === 0) return;
29184
+ const ends = new Float32Array(activeSegments.length * SEGMENT_FLOATS);
29185
+ let count = 0;
29186
+ for (const { p0, p1 } of activeSegments) {
29187
+ if (Math.hypot(p1.x - p0.x, p1.y - p0.y) < 1e-6) continue;
29188
+ const at = count++ * SEGMENT_FLOATS;
29189
+ ends[at] = p0.x;
29190
+ ends[at + 1] = p0.y;
29191
+ ends[at + 2] = p1.x;
29192
+ ends[at + 3] = p1.y;
29193
+ }
29194
+ if (count === 0) return;
28987
29195
  const c = parseColor(color);
28988
- const opacity = opts?.opacity ?? 1;
28989
- let capType = 0;
28990
- if (opts?.strokeLineCap === "butt") capType = 1;
28991
- else if (opts?.strokeLineCap === "square") capType = 2;
28992
- for (const segment of activeSegments) this.drawSegment(pass, transformStack, segment.p0, segment.p1, c, strokeWidth, capType, surfaceWidth, surfaceHeight, opacity, opts);
28993
- }
28994
- drawSegment(pass, transformStack, p0, p1, color, strokeWidth, capType, surfaceWidth, surfaceHeight, opacity, opts) {
28995
29196
  const data = this.scratchData;
28996
- transformStack.packIntoBuffer(data, opacity, surfaceWidth, surfaceHeight, opts?.transform);
29197
+ transformStack.packIntoBuffer(data, opts?.opacity ?? 1, surfaceWidth, surfaceHeight, opts?.transform);
28997
29198
  data[15] = strokeWidth;
28998
- data[16] = color.r;
28999
- data[17] = color.g;
29000
- data[18] = color.b;
29001
- data[19] = color.a;
29002
- data[20] = capType;
29199
+ data[16] = c.r;
29200
+ data[17] = c.g;
29201
+ data[18] = c.b;
29202
+ data[19] = c.a;
29203
+ data[20] = opts?.strokeLineCap === "butt" ? 1 : opts?.strokeLineCap === "square" ? 2 : 0;
29003
29204
  data[21] = 0;
29004
29205
  data[22] = 0;
29005
29206
  data[23] = 0;
29006
- data[24] = p0.x;
29007
- data[25] = p0.y;
29008
- data[26] = p1.x;
29009
- data[27] = p1.y;
29010
29207
  const uniformBuffer = this.uniformPool.getBuffer(this.device, data);
29011
- const dx = p1.x - p0.x;
29012
- const dy = p1.y - p0.y;
29013
- const len = Math.sqrt(dx * dx + dy * dy);
29014
- if (len < 1e-6) return;
29015
- const padding = strokeWidth * .5 + 2;
29016
- const nx = dx / len * padding;
29017
- const ny = dy / len * padding;
29018
- const px = -ny;
29019
- const py = nx;
29020
- const vertexData = new Float32Array([
29021
- p0.x - nx - px,
29022
- p0.y - ny - py,
29023
- p0.x - nx + px,
29024
- p0.y - ny + py,
29025
- p1.x + nx - px,
29026
- p1.y + ny - py,
29027
- p1.x + nx + px,
29028
- p1.y + ny + py
29029
- ]);
29030
- const vertexBuffer = this.vertexPool.getBuffer(this.device, vertexData);
29031
- const pipeline = opts?.blendMode === "max" ? this.maxPipeline : this.pipeline;
29032
- pass.setPipeline(pipeline);
29033
- const uniformBindGroup = this.uniformBindGroupCache.getBindGroup(this.device, this.uniformLayout, uniformBuffer);
29034
- pass.setBindGroup(0, uniformBindGroup);
29035
- pass.setVertexBuffer(0, vertexBuffer);
29036
- pass.draw(4);
29208
+ const segmentBuffer = this.segmentPool.getBuffer(this.device, ends.subarray(0, count * SEGMENT_FLOATS));
29209
+ const useMax = opts?.blendMode === "max";
29210
+ pass.setPipeline(useMax ? this.maxPipeline : this.pipeline);
29211
+ pass.setBindGroup(0, this.uniformBindGroupCache.getBindGroup(this.device, this.uniformLayout, uniformBuffer));
29212
+ pass.setVertexBuffer(0, segmentBuffer);
29213
+ pass.draw(4, count);
29037
29214
  }
29038
29215
  destroy() {
29039
29216
  this.uniformPool.destroy();
29040
- this.vertexPool.destroy();
29217
+ this.segmentPool.destroy();
29041
29218
  this.uniformBindGroupCache.destroy();
29042
29219
  }
29043
29220
  };
@@ -30806,8 +30983,13 @@ var BloomPipeline = class {
30806
30983
  module;
30807
30984
  pipelineLayout;
30808
30985
  sampler;
30809
- mips = [];
30810
- mipKey = "";
30986
+ /**
30987
+ * Mip chains by size and depth. Scenes with different bloom share a frame
30988
+ * (one command buffer), so a chain stays alive until the frame is done:
30989
+ * chains not used in a frame are destroyed at the next `resetPools`.
30990
+ */
30991
+ chains = /* @__PURE__ */ new Map();
30992
+ usedThisFrame = /* @__PURE__ */ new Set();
30811
30993
  placeholder;
30812
30994
  uniformPool = new BufferPool(48, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
30813
30995
  constructor(device) {
@@ -30910,17 +31092,18 @@ var BloomPipeline = class {
30910
31092
  }
30911
31093
  return pipe$1;
30912
31094
  }
30913
- ensureMips(width, height, levels) {
31095
+ mipChain(width, height, levels) {
30914
31096
  const key = `${width}x${height}x${levels}`;
30915
- if (key === this.mipKey) return;
30916
- for (const t of this.mips) t.destroy();
30917
- this.mips = [];
31097
+ this.usedThisFrame.add(key);
31098
+ const cached$1 = this.chains.get(key);
31099
+ if (cached$1) return cached$1;
31100
+ const chain = [];
30918
31101
  let w = width;
30919
31102
  let h = height;
30920
31103
  for (let i = 0; i < levels; i++) {
30921
31104
  w = Math.max(1, Math.floor(w / 2));
30922
31105
  h = Math.max(1, Math.floor(h / 2));
30923
- this.mips.push(this.device.createTexture({
31106
+ chain.push(this.device.createTexture({
30924
31107
  label: `BloomMip${i}`,
30925
31108
  size: {
30926
31109
  width: w,
@@ -30931,7 +31114,8 @@ var BloomPipeline = class {
30931
31114
  }));
30932
31115
  if (w === 1 && h === 1) break;
30933
31116
  }
30934
- this.mipKey = key;
31117
+ this.chains.set(key, chain);
31118
+ return chain;
30935
31119
  }
30936
31120
  uniforms(texelW, texelH, opts) {
30937
31121
  const data = new Float32Array([
@@ -31009,8 +31193,7 @@ var BloomPipeline = class {
31009
31193
  mode: display ? 1 : 0
31010
31194
  };
31011
31195
  const levels = Math.max(1, Math.min(8, Math.round(options.radius ?? 6)));
31012
- this.ensureMips(source.width, source.height, levels);
31013
- const mips = this.mips;
31196
+ const mips = this.mipChain(source.width, source.height, levels);
31014
31197
  const u = (w, h) => this.uniforms(1 / w, 1 / h, opts);
31015
31198
  const glowSource = hdrSource ?? source;
31016
31199
  this.pass(encoder, this.prefilterPipe, mips[0].createView(), glowSource, this.placeholder, u(glowSource.width, glowSource.height), "clear");
@@ -31024,12 +31207,22 @@ var BloomPipeline = class {
31024
31207
  }
31025
31208
  this.pass(encoder, this.compositePipe(dest.format), dest.createView(), mips[0], source, u(mips[0].width, mips[0].height), "clear");
31026
31209
  }
31027
- /** Call once per frame before `apply`: recycles the per-pass uniforms. */
31210
+ /**
31211
+ * Call once per frame before `apply`: recycles the per-pass uniforms and
31212
+ * frees the mip chains the last frame did not use.
31213
+ */
31028
31214
  resetPools() {
31029
31215
  this.uniformPool.reset();
31216
+ for (const [key, chain] of this.chains) {
31217
+ if (this.usedThisFrame.has(key)) continue;
31218
+ for (const t of chain) t.destroy();
31219
+ this.chains.delete(key);
31220
+ }
31221
+ this.usedThisFrame.clear();
31030
31222
  }
31031
31223
  destroy() {
31032
- for (const t of this.mips) t.destroy();
31224
+ for (const chain of this.chains.values()) for (const t of chain) t.destroy();
31225
+ this.chains.clear();
31033
31226
  this.placeholder.destroy();
31034
31227
  this.uniformPool.destroy();
31035
31228
  }
@@ -31418,15 +31611,56 @@ struct Shaded {
31418
31611
  hdr : vec3<f32>,
31419
31612
  };
31420
31613
 
31614
+ // Two-sided, art-directed diffuse lighting. Shares the full direct-light
31615
+ // storage, range/spot attenuation and PCF shadows with the physical renderer.
31616
+ fn shadeToon(worldPos: vec3<f32>, worldNormal: vec3<f32>, base: vec3<f32>, ambient: f32, shade: vec4<f32>, lightTint: vec4<f32>, params: vec4<f32>) -> vec3<f32> {
31617
+ if (lights.params.y < 0.5) { return base; }
31618
+ var N = normalize(worldNormal);
31619
+ let V = normalize(cam.cameraPos.xyz - worldPos);
31620
+ if (dot(N, V) < 0.0) { N = -N; }
31621
+ var illumination = lights.ambientColor.rgb * ambient * lightTint.rgb;
31622
+ var visibility = clamp(max(illumination.r, max(illumination.g, illumination.b)), 0.0, 1.0);
31623
+ for (var i = 0; i < i32(lights.params.x); i++) {
31624
+ let ls = sampleLight(i, worldPos);
31625
+ if (!ls.valid) { continue; }
31626
+ let cosine = dot(N, ls.L);
31627
+ let softness = max(params.x, 0.0001);
31628
+ let band = smoothstep(lightTint.w - softness * 0.5, lightTint.w + softness * 0.5, cosine);
31629
+ let light = directLights[i];
31630
+ let strength = band * ls.attenuation * light.colorIntensity.w * shadowFactor(worldPos, N, i);
31631
+ let contribution = light.colorIntensity.rgb * lightTint.rgb * strength * mix(1.0, max(cosine, 0.0), shade.w);
31632
+ // Remove the shadow floor only as actual modeled light arrives.
31633
+ // Using the pre-diffuse band here creates a dark ring between the
31634
+ // shadow floor and the lit region, which slides as a surface turns.
31635
+ let energy = max(contribution.r, max(contribution.g, contribution.b));
31636
+ visibility = max(visibility, clamp(energy, 0.0, 1.0));
31637
+ illumination += contribution;
31638
+ }
31639
+ return base * (shade.rgb * (1.0 - visibility) + illumination);
31640
+ }
31641
+
31642
+ // The existing linear-depth attachment doubles as a geometry buffer:
31643
+ // r = optical depth, gb = octahedral world normal, a = outline participation.
31644
+ // Geometric normals deliberately ignore normal maps and texture seams.
31645
+ fn geometryPixel(depth: f32, normal: vec3<f32>, outlined: f32) -> vec4<f32> {
31646
+ let n = normalize(normal);
31647
+ let p = n.xy / max(abs(n.x) + abs(n.y) + abs(n.z), 1e-6);
31648
+ let signs = select(vec2<f32>(-1.0), vec2<f32>(1.0), p >= vec2<f32>(0.0));
31649
+ let oct = select((1.0 - abs(p.yx)) * signs, p, n.z >= 0.0);
31650
+ return vec4<f32>(depth, oct * 0.5 + 0.5, outlined);
31651
+ }
31652
+
31421
31653
  /// Shades a surface for the scene's mode.
31422
31654
  /// material: x shininess, y specularIntensity, z ambientIntensity, w mode (0 unlit).
31423
31655
  /// pbr: x roughness (<0 = default), y metallic, z/w unused.
31424
31656
  /// emissive: rgb sRGB color, w strength.
31425
- fn shadeSurfaceFull(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3<f32>, material: vec4<f32>, pbr: vec4<f32>, emissive: vec4<f32>) -> Shaded {
31657
+ fn shadeSurfaceFull(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3<f32>, material: vec4<f32>, pbr: vec4<f32>, emissive: vec4<f32>, toonShadow: vec4<f32>, toonLight: vec4<f32>, toonParams: vec4<f32>) -> Shaded {
31426
31658
  var out: Shaded;
31427
31659
  if (lights.params.z < 0.5) {
31428
31660
  var c = baseColor;
31429
- if (material.w >= 0.5) {
31661
+ if (material.w > 1.5) {
31662
+ c = shadeToon(worldPos, worldNormal, baseColor, material.z, toonShadow, toonLight, toonParams);
31663
+ } else if (material.w >= 0.5) {
31430
31664
  c = applyLighting3D(worldPos, worldNormal, baseColor, material);
31431
31665
  }
31432
31666
  c += emissive.rgb * emissive.w;
@@ -31437,6 +31671,8 @@ fn shadeSurfaceFull(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3
31437
31671
  var radiance: vec3<f32>;
31438
31672
  if (material.w < 0.5) {
31439
31673
  radiance = srgbToLinear(baseColor);
31674
+ } else if (material.w > 1.5) {
31675
+ radiance = srgbToLinear(shadeToon(worldPos, worldNormal, baseColor, material.z, toonShadow, toonLight, toonParams));
31440
31676
  } else {
31441
31677
  let rough = select(pbr.x, 0.5, pbr.x < 0.0);
31442
31678
  radiance = shadePhysical(worldPos, worldNormal, srgbToLinear(baseColor), rough, clamp(pbr.y, 0.0, 1.0), material.z);
@@ -31472,9 +31708,6 @@ fn shadeSurfaceFull(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3
31472
31708
  return out;
31473
31709
  }
31474
31710
 
31475
- fn shadeSurface(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3<f32>, material: vec4<f32>, pbr: vec4<f32>, emissive: vec4<f32>) -> vec3<f32> {
31476
- return shadeSurfaceFull(worldPos, worldNormal, baseColor, material, pbr, emissive).display;
31477
- }
31478
31711
  `;
31479
31712
  /** Group-3 bindings every lit 3D shader declares. */
31480
31713
  const lightsBindingsWgsl = `
@@ -31785,7 +32018,7 @@ fn fs_mrt(in: VsOut) -> FsOutputMrt {
31785
32018
  var out: FsOutputMrt;
31786
32019
  let s = shadeFloor(in);
31787
32020
  out.color = vec4<f32>(s.display * s.alpha, s.alpha);
31788
- out.linearDepth = vec4<f32>(s.linearDepth, 0.0, 0.0, 1.0);
32021
+ out.linearDepth = geometryPixel(s.linearDepth, vec3<f32>(0.0, -1.0, 0.0), 1.0);
31789
32022
  out.depth = s.depth;
31790
32023
  return out;
31791
32024
  }
@@ -31805,7 +32038,7 @@ fn fs_mrt_bloom(in: VsOut) -> FsOutputMrtBloom {
31805
32038
  var out: FsOutputMrtBloom;
31806
32039
  let s = shadeFloor(in);
31807
32040
  out.color = vec4<f32>(s.display * s.alpha, s.alpha);
31808
- out.linearDepth = vec4<f32>(s.linearDepth, 0.0, 0.0, 1.0);
32041
+ out.linearDepth = geometryPixel(s.linearDepth, vec3<f32>(0.0, -1.0, 0.0), 1.0);
31809
32042
  out.hdr = vec4<f32>(s.hdr * s.alpha, 1.0);
31810
32043
  out.depth = s.depth;
31811
32044
  return out;
@@ -32525,6 +32758,9 @@ struct ModelUniforms {
32525
32758
  shade : vec4<f32>, // rgb = toon shadow-side color multiplier, a = alpha cutoff (0 = none)
32526
32759
  pbr : vec4<f32>, // x = roughness (<0 = default), y = metallic
32527
32760
  emissive : vec4<f32>, // rgb = sRGB emissive color, w = strength
32761
+ toonShadow : vec4<f32>, // rgb shadow-side multiplier, w diffuse modelling
32762
+ toonLight : vec4<f32>, // rgb lit-side multiplier, w terminator threshold
32763
+ toonParams : vec4<f32>, // x terminator softness, y outline participation
32528
32764
  maps : vec4<f32>, // x = normal scale (0 = no map), y = occlusion strength, z = has metallic-roughness map, w = has emissive map
32529
32765
  };
32530
32766
 
@@ -32665,37 +32901,6 @@ fn vs_main(
32665
32901
  return out;
32666
32902
  }
32667
32903
 
32668
- // Toon (MToon-style): the base color as authored where the light reaches,
32669
- // the shade color past a soft terminator. Highlights stay out: the texture
32670
- // already paints them.
32671
- fn toonLighting(in: VertexOutput, baseColor: vec3<f32>) -> vec3<f32> {
32672
- var N = normalize(in.worldNormal);
32673
- let V = normalize(cam.cameraPos.xyz - in.worldPos);
32674
- if (dot(N, V) < 0.0) {
32675
- N = -N;
32676
- }
32677
- var lit = 0.0;
32678
- let numLights = i32(lights.params.x);
32679
- for (var i = 0; i < 8; i++) {
32680
- if (i >= numLights) {
32681
- break;
32682
- }
32683
- let light = lights.lights[i];
32684
- let lightType = light.posType.w;
32685
- var L = vec3<f32>(0.0, 0.0, 1.0);
32686
- if (lightType > 0.5 && lightType < 1.5) {
32687
- L = normalize(-light.dirRadius.xyz);
32688
- } else if (lightType >= 1.5) {
32689
- L = normalize(light.posType.xyz - in.worldPos);
32690
- } else {
32691
- continue;
32692
- }
32693
- let terminator = smoothstep(-0.05, 0.2, dot(N, L));
32694
- lit = max(lit, terminator * min(light.colorIntensity.w, 1.0));
32695
- }
32696
- return baseColor * mix(model.shade.rgb, vec3<f32>(1.0), lit);
32697
- }
32698
-
32699
32904
  struct MeshFragment {
32700
32905
  color : vec4<f32>, // premultiplied display color
32701
32906
  hdr : vec4<f32>, // premultiplied scene radiance (bloom source)
@@ -32763,7 +32968,7 @@ fn shadeGlass(in: VertexOutput, Nin: vec3<f32>, tint: vec3<f32>, alpha: f32, rim
32763
32968
  let transmitted = col * depthTint * (1.0 - 0.45 * rim * rim);
32764
32969
  let f0 = pow((ior - 1.0) / (ior + 1.0), 2.0);
32765
32970
  let F = f0 + (1.0 - f0) * pow(rim, 5.0);
32766
- let lit = shadeSurfaceFull(in.worldPos, N, vec3<f32>(0.0), model.materialParams, vec4<f32>(model.pbr.x, 0.0, 0.0, 0.0), vec4<f32>(0.0));
32971
+ let lit = shadeSurfaceFull(in.worldPos, N, vec3<f32>(0.0), model.materialParams, vec4<f32>(model.pbr.x, 0.0, 0.0, 0.0), vec4<f32>(0.0), model.toonShadow, model.toonLight, model.toonParams);
32767
32972
  // Grazing faces mirror the room; keep that faint so dark scenes stay dark
32768
32973
  // and the studio highlights below carry the shape.
32769
32974
  let sheen = vec3<f32>(0.9, 0.94, 1.0) * F * 0.22 + tint * 0.05 * (0.4 + rim);
@@ -32811,19 +33016,6 @@ fn shadeMesh(in: VertexOutput, isFront: bool) -> MeshFragment {
32811
33016
 
32812
33017
  let baseRgb = texColor.rgb * in.color.rgb * model.colorTint.rgb;
32813
33018
  var out: MeshFragment;
32814
- if (model.materialParams.w > 1.5) {
32815
- var toon = baseRgb;
32816
- if (lights.params.y >= 0.5) {
32817
- toon = toonLighting(in, baseRgb);
32818
- }
32819
- if (model.params.w > 0.5 && (in.emissive.w > 0.0 || dot(in.emissive.rgb, in.emissive.rgb) > 0.0)) {
32820
- toon += in.emissive.rgb * in.emissive.w;
32821
- }
32822
- out.color = vec4<f32>(toon * alpha, alpha);
32823
- out.hdr = vec4<f32>(srgbToLinear(toon) * alpha, alpha);
32824
- return out;
32825
- }
32826
-
32827
33019
  var N = normalize(in.worldNormal);
32828
33020
  if (model.maps.x > 0.0) {
32829
33021
  var tn = normalSample * 2.0 - 1.0;
@@ -32853,7 +33045,7 @@ fn shadeMesh(in: VertexOutput, isFront: bool) -> MeshFragment {
32853
33045
  if (model.params.w > 0.5 && (in.emissive.w > 0.0 || dot(in.emissive.rgb, in.emissive.rgb) > 0.0)) {
32854
33046
  emissive = in.emissive;
32855
33047
  }
32856
- let shaded = shadeSurfaceFull(in.worldPos, N, baseRgb, material, pbr, emissive);
33048
+ let shaded = shadeSurfaceFull(in.worldPos, N, baseRgb, material, pbr, emissive, model.toonShadow, model.toonLight, model.toonParams);
32857
33049
  out.color = vec4<f32>(shaded.display * alpha, alpha);
32858
33050
  out.hdr = vec4<f32>(shaded.hdr * alpha, alpha);
32859
33051
  return out;
@@ -32884,7 +33076,7 @@ fn fs_single(in: VertexOutput, @builtin(front_facing) isFront: bool) -> @locatio
32884
33076
  fn fs_mrt(in: VertexOutput, @builtin(front_facing) isFront: bool) -> MrtOutput {
32885
33077
  var out: MrtOutput;
32886
33078
  out.color = shadeMesh(in, isFront).color;
32887
- out.depth = vec4<f32>(in.linearDepth, 0.0, 0.0, 1.0);
33079
+ out.depth = geometryPixel(in.linearDepth, in.worldNormal, model.toonParams.y);
32888
33080
  return out;
32889
33081
  }
32890
33082
 
@@ -32902,7 +33094,7 @@ fn fs_mrt_bloom(in: VertexOutput, @builtin(front_facing) isFront: bool) -> MrtBl
32902
33094
  let f = shadeMesh(in, isFront);
32903
33095
  var out: MrtBloomOutput;
32904
33096
  out.color = f.color;
32905
- out.depth = vec4<f32>(in.linearDepth, 0.0, 0.0, 1.0);
33097
+ out.depth = geometryPixel(in.linearDepth, in.worldNormal, model.toonParams.y);
32906
33098
  out.hdr = f.hdr;
32907
33099
  return out;
32908
33100
  }
@@ -32927,6 +33119,9 @@ struct ModelUniforms {
32927
33119
  materialParams : vec4<f32>, // x = shininess, y = specularIntensity, z = ambientIntensity, w = materialMode (0=unlit, 1=lit)
32928
33120
  pbr : vec4<f32>, // x = roughness (<0 = default), y = metallic
32929
33121
  emissive : vec4<f32>, // rgb = sRGB emissive color, w = strength
33122
+ toonShadow : vec4<f32>, // rgb shadow-side multiplier, w diffuse modelling
33123
+ toonLight : vec4<f32>, // rgb lit-side multiplier, w terminator threshold
33124
+ toonParams : vec4<f32>, // x terminator softness, y outline participation
32930
33125
  relief : vec4<f32>, // x = amount (px), y = on, w = grid cells per side
32931
33126
  reliefRect : vec4<f32>, // xy = where the layer's box starts in reliefTex (0..1), zw = its size
32932
33127
  reliefEye : vec4<f32>, // xyz = the eye the surface keeps its look from (the default camera)
@@ -33121,7 +33316,7 @@ fn shadeQuad(in: VertexOutput, isFront: bool) -> QuadFragment {
33121
33316
  discard;
33122
33317
  }
33123
33318
 
33124
- let shaded = shadeSurfaceFull(in.worldPos, in.worldNormal, baseRgb, model.materialParams, model.pbr, model.emissive);
33319
+ let shaded = shadeSurfaceFull(in.worldPos, in.worldNormal, baseRgb, model.materialParams, model.pbr, model.emissive, model.toonShadow, model.toonLight, model.toonParams);
33125
33320
  var out: QuadFragment;
33126
33321
  out.color = vec4<f32>(shaded.display * alpha, alpha);
33127
33322
  out.hdr = vec4<f32>(shaded.hdr * alpha, alpha);
@@ -33153,7 +33348,7 @@ fn fs_single(in: VertexOutput, @builtin(front_facing) isFront: bool) -> @locatio
33153
33348
  fn fs_mrt(in: VertexOutput, @builtin(front_facing) isFront: bool) -> MrtOutput {
33154
33349
  var out: MrtOutput;
33155
33350
  out.color = shadeQuad(in, isFront).color;
33156
- out.depth = vec4<f32>(in.linearDepth, 0.0, 0.0, 1.0);
33351
+ out.depth = geometryPixel(in.linearDepth, in.worldNormal, model.toonParams.y);
33157
33352
  return out;
33158
33353
  }
33159
33354
 
@@ -33171,7 +33366,7 @@ fn fs_mrt_bloom(in: VertexOutput, @builtin(front_facing) isFront: bool) -> MrtBl
33171
33366
  let f = shadeQuad(in, isFront);
33172
33367
  var out: MrtBloomOutput;
33173
33368
  out.color = f.color;
33174
- out.depth = vec4<f32>(in.linearDepth, 0.0, 0.0, 1.0);
33369
+ out.depth = geometryPixel(in.linearDepth, in.worldNormal, model.toonParams.y);
33175
33370
  out.hdr = f.hdr;
33176
33371
  return out;
33177
33372
  }
@@ -33197,13 +33392,37 @@ const SHADOW_DEPTH_STENCIL = {
33197
33392
  depthBiasSlopeScale: 2.5,
33198
33393
  depthBiasClamp: .01
33199
33394
  };
33200
- /** Floats appended to each 3D model uniform block: pbr vec4 + emissive vec4. */
33201
- const SURFACE_PARAMS_FLOATS = 8;
33202
- function packSurfaceParams(data, out, offset) {
33395
+ /** Shared model uniforms: PBR, emissive, and three art-direction vec4s. */
33396
+ const SURFACE_PARAMS_FLOATS = 20;
33397
+ function packSurfaceParams(data, out, offset, modelShade) {
33203
33398
  out[offset] = data.roughness ?? -1;
33204
33399
  out[offset + 1] = data.metallic ?? 0;
33205
33400
  out[offset + 2] = 0;
33206
33401
  out[offset + 3] = 0;
33402
+ const shadow = data.toon?.shadowColor ? parseColor(data.toon.shadowColor) : null;
33403
+ out.set(shadow ? [
33404
+ shadow.r,
33405
+ shadow.g,
33406
+ shadow.b
33407
+ ] : modelShade ?? [
33408
+ .5,
33409
+ .5,
33410
+ .55
33411
+ ], offset + 8);
33412
+ out[offset + 11] = data.toon?.diffuse ?? 0;
33413
+ const light = parseColor(data.toon?.lightColor ?? "#ffffff");
33414
+ out.set([
33415
+ light.r,
33416
+ light.g,
33417
+ light.b,
33418
+ data.toon?.threshold ?? .075
33419
+ ], offset + 12);
33420
+ out.set([
33421
+ data.toon?.softness ?? .25,
33422
+ data.outline === false ? 0 : 1,
33423
+ 0,
33424
+ 0
33425
+ ], offset + 16);
33207
33426
  const e = data.emissive;
33208
33427
  if (e === void 0 || e === null) {
33209
33428
  out.fill(0, offset + 4, offset + 8);
@@ -33272,7 +33491,7 @@ function packMaterialParams(data) {
33272
33491
  data.roughness !== void 0 ? Math.max(1, (1 - data.roughness) * 128) : data.shininess ?? 32,
33273
33492
  data.metallic !== void 0 ? data.metallic : data.specularIntensity ?? .5,
33274
33493
  data.ambientIntensity ?? 1,
33275
- data.material === "unlit" ? 0 : 1
33494
+ data.material === "unlit" ? 0 : data.material === "toon" ? 2 : 1
33276
33495
  ];
33277
33496
  }
33278
33497
  var Quad3DPipeline = class {
@@ -34002,12 +34221,13 @@ var Mesh3DPipeline = class {
34002
34221
  this.modelData[45] = shade[1];
34003
34222
  this.modelData[46] = shade[2];
34004
34223
  this.modelData[47] = data.alphaCutoff ?? 0;
34005
- packSurfaceParams(data, this.modelData, 48);
34224
+ packSurfaceParams(data, this.modelData, 48, data.shadeColor);
34006
34225
  const maps = data.maps;
34007
- this.modelData[56] = maps?.normal ? maps.normalScale ?? 1 : 0;
34008
- this.modelData[57] = maps?.occlusion ? maps.occlusionStrength ?? 1 : 0;
34009
- this.modelData[58] = maps?.metallicRoughness ? 1 : 0;
34010
- this.modelData[59] = maps?.emissive ? 1 : 0;
34226
+ const mapsOffset = 48 + SURFACE_PARAMS_FLOATS;
34227
+ this.modelData[mapsOffset] = maps?.normal ? maps.normalScale ?? 1 : 0;
34228
+ this.modelData[mapsOffset + 1] = maps?.occlusion ? maps.occlusionStrength ?? 1 : 0;
34229
+ this.modelData[mapsOffset + 2] = maps?.metallicRoughness ? 1 : 0;
34230
+ this.modelData[mapsOffset + 3] = maps?.emissive ? 1 : 0;
34011
34231
  const glass = data.glass;
34012
34232
  if (glass) {
34013
34233
  this.modelData[44] = glass.dispersion;
@@ -37786,6 +38006,309 @@ fn fs_dof(in: VSOut) -> @location(0) vec4<f32> {
37786
38006
  return accumColor / totalWeight;
37787
38007
  }
37788
38008
  `;
38009
+ function outlineShader(sampleCount) {
38010
+ const multisampled = sampleCount > 1;
38011
+ const textureType = multisampled ? "texture_multisampled_2d<f32>" : "texture_2d<f32>";
38012
+ return `
38013
+ struct Uniforms {
38014
+ params : vec4<f32>, // width, cosine(crease angle), relative depth threshold, opacity
38015
+ ink : vec4<f32>,
38016
+ fog : vec4<f32>,
38017
+ fogRange : vec4<f32>,
38018
+ projection : vec4<f32>, // inverse projection x/y scale, far depth
38019
+ };
38020
+ @group(0) @binding(0) var<uniform> u : Uniforms;
38021
+ @group(0) @binding(1) var colorTex : ${textureType};
38022
+ @group(0) @binding(2) var geometryTex : ${textureType};
38023
+
38024
+ @vertex fn vs(@builtin(vertex_index) i : u32) -> @builtin(position) vec4<f32> {
38025
+ let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
38026
+ return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
38027
+ }
38028
+
38029
+ fn geometry(p : vec2<i32>, sample : i32) -> vec4<f32> {
38030
+ let size = vec2<i32>(textureDimensions(geometryTex));
38031
+ // Beyond the viewport is the same surface: never ink the canvas border.
38032
+ return textureLoad(geometryTex, clamp(p, vec2<i32>(0), size - 1), sample);
38033
+ }
38034
+
38035
+ fn normal(oct : vec2<f32>) -> vec3<f32> {
38036
+ let p = oct * 2.0 - 1.0;
38037
+ var n = vec3<f32>(p, 1.0 - abs(p.x) - abs(p.y));
38038
+ let t = max(-n.z, 0.0);
38039
+ n = vec3<f32>(n.xy + select(vec2<f32>(t), vec2<f32>(-t), n.xy >= vec2<f32>(0.0)), n.z);
38040
+ return normalize(n);
38041
+ }
38042
+
38043
+ fn srgbToLinear(c : vec3<f32>) -> vec3<f32> {
38044
+ return select(pow((max(c, vec3<f32>(0.0)) + 0.055) / 1.055, vec3<f32>(2.4)), c / 12.92, c <= vec3<f32>(0.04045));
38045
+ }
38046
+ fn linearToSrgb(c : vec3<f32>) -> vec3<f32> {
38047
+ let x = max(c, vec3<f32>(0.0));
38048
+ return select(1.055 * pow(x, vec3<f32>(1.0 / 2.4)) - 0.055, x * 12.92, x <= vec3<f32>(0.0031308));
38049
+ }
38050
+
38051
+ fn edge(center : vec4<f32>, next : vec4<f32>, previous : vec4<f32>) -> f32 {
38052
+ let z = 1.0 / max(center.r, 0.0001);
38053
+ let a = 1.0 / max(next.r, 0.0001);
38054
+ let b = 1.0 / max(previous.r, 0.0001);
38055
+ // Inverse depth is affine on a perspective-projected plane. Its signed
38056
+ // second difference suppresses grazing surfaces and inks only the near
38057
+ // side of an occlusion, including thin geometry with background both sides.
38058
+ let curvature = (2.0 * z - a - b) / z;
38059
+ let depthEdge = smoothstep(u.params.z, u.params.z * 2.0, curvature);
38060
+ let sameDepth = abs(center.r - next.r) / max(center.r, 0.0001) < 0.03;
38061
+ let crease = select(0.0, 1.0, next.a > 0.5 && sameDepth && dot(normal(center.gb), normal(next.gb)) < u.params.y);
38062
+ return max(depthEdge, crease);
38063
+ }
38064
+
38065
+ @fragment fn fs(@builtin(position) pos : vec4<f32>${multisampled ? ", @builtin(sample_index) sampleIndex : u32" : ""}) -> @location(0) vec4<f32> {
38066
+ let sample = ${multisampled ? "i32(sampleIndex)" : "0"};
38067
+ let pixel = vec2<i32>(pos.xy);
38068
+ let source = textureLoad(colorTex, pixel, sample);
38069
+ let c = geometry(pixel, sample);
38070
+ if (c.a <= 0.0 || u.params.x <= 0.0 || u.params.w <= 0.0) { return source; }
38071
+ var coverage = 0.0;
38072
+ let directions = array<vec2<f32>, 8>(
38073
+ vec2<f32>(1.0, 0.0), vec2<f32>(-1.0, 0.0), vec2<f32>(0.0, 1.0), vec2<f32>(0.0, -1.0),
38074
+ vec2<f32>(0.707107, 0.707107), vec2<f32>(-0.707107, 0.707107), vec2<f32>(0.707107, -0.707107), vec2<f32>(-0.707107, -0.707107));
38075
+ for (var r = 1; r <= i32(ceil(u.params.x)); r++) {
38076
+ let weight = clamp(u.params.x - f32(r) + 1.0, 0.0, 1.0);
38077
+ for (var d = 0; d < 8; d++) {
38078
+ let offset = vec2<i32>(round(directions[d] * f32(r)));
38079
+ coverage = max(coverage, edge(c, geometry(pixel + offset, sample), geometry(pixel - offset, sample)) * weight);
38080
+ }
38081
+ }
38082
+ var ink = u.ink.rgb;
38083
+ if (u.fog.w > 0.5) {
38084
+ let uv = pos.xy / vec2<f32>(textureDimensions(colorTex));
38085
+ let ray = vec3<f32>((uv * 2.0 - 1.0) * u.projection.xy, 1.0);
38086
+ let distance = c.r * length(ray);
38087
+ let fog = smoothstep(u.fogRange.x, max(u.fogRange.y, u.fogRange.x + 1.0), distance) * u.fogRange.z;
38088
+ // Match surface fog's linear-light blend; display-space interpolation
38089
+ // would darken outlines halfway into a light-colored atmosphere.
38090
+ ink = linearToSrgb(mix(srgbToLinear(ink), srgbToLinear(u.fog.rgb), fog));
38091
+ }
38092
+ let visibleCoverage = min(c.a / max(source.a, 0.0001), 1.0);
38093
+ return vec4<f32>(mix(source.rgb, ink * source.a, coverage * visibleCoverage * u.params.w * u.ink.a), source.a);
38094
+ }
38095
+ `;
38096
+ }
38097
+ /** Ink shades original color/depth/normal samples, then resolves color with MSAA. */
38098
+ var GeometryOutlinePipeline = class {
38099
+ layout;
38100
+ pipeline;
38101
+ multisampledOutput = null;
38102
+ pool = new BufferPool(80, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
38103
+ constructor(device, format, sampleCount = 1) {
38104
+ this.device = device;
38105
+ this.format = format;
38106
+ this.sampleCount = sampleCount;
38107
+ this.layout = device.createBindGroupLayout({
38108
+ label: "GeometryOutlineLayout",
38109
+ entries: [{
38110
+ binding: 0,
38111
+ visibility: GPUShaderStage.FRAGMENT,
38112
+ buffer: { type: "uniform" }
38113
+ }, ...[1, 2].map((binding) => ({
38114
+ binding,
38115
+ visibility: GPUShaderStage.FRAGMENT,
38116
+ texture: {
38117
+ sampleType: "unfilterable-float",
38118
+ multisampled: sampleCount > 1
38119
+ }
38120
+ }))]
38121
+ });
38122
+ const module$1 = device.createShaderModule({
38123
+ label: "geometry-outlines.wgsl",
38124
+ code: outlineShader(sampleCount)
38125
+ });
38126
+ this.pipeline = device.createRenderPipeline({
38127
+ label: "GeometryOutlines",
38128
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.layout] }),
38129
+ vertex: {
38130
+ module: module$1,
38131
+ entryPoint: "vs"
38132
+ },
38133
+ fragment: {
38134
+ module: module$1,
38135
+ entryPoint: "fs",
38136
+ targets: [{ format }]
38137
+ },
38138
+ primitive: { topology: "triangle-list" },
38139
+ multisample: { count: sampleCount }
38140
+ });
38141
+ }
38142
+ reset() {
38143
+ this.pool.reset();
38144
+ }
38145
+ destroy() {
38146
+ this.pool.destroy();
38147
+ this.multisampledOutput?.destroy();
38148
+ }
38149
+ attachment(source, dest) {
38150
+ if (this.sampleCount <= 1) return {
38151
+ view: dest,
38152
+ loadOp: "clear",
38153
+ storeOp: "store"
38154
+ };
38155
+ if (!this.multisampledOutput || this.multisampledOutput.width !== source.width || this.multisampledOutput.height !== source.height) {
38156
+ this.multisampledOutput?.destroy();
38157
+ this.multisampledOutput = this.device.createTexture({
38158
+ label: "GeometryOutlineMsaaOutput",
38159
+ size: {
38160
+ width: source.width,
38161
+ height: source.height
38162
+ },
38163
+ format: this.format,
38164
+ sampleCount: this.sampleCount,
38165
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
38166
+ });
38167
+ }
38168
+ return {
38169
+ view: this.multisampledOutput.createView(),
38170
+ resolveTarget: dest,
38171
+ loadOp: "clear",
38172
+ storeOp: "discard"
38173
+ };
38174
+ }
38175
+ render(encoder, source, geometry, dest, camera, options, fog) {
38176
+ const data = new Float32Array(20);
38177
+ data.set([
38178
+ (options.width ?? 1.5) * source.height / (options.referenceHeight ?? 1080),
38179
+ Math.cos((options.creaseAngle ?? 45) * Math.PI / 180),
38180
+ options.depthThreshold ?? .01,
38181
+ options.opacity ?? 1
38182
+ ]);
38183
+ const color = parseColor(options.color ?? "#16131c");
38184
+ data.set([
38185
+ color.r,
38186
+ color.g,
38187
+ color.b,
38188
+ color.a
38189
+ ], 4);
38190
+ if (fog) {
38191
+ const tint = parseColor(fog.color);
38192
+ data.set([
38193
+ tint.r,
38194
+ tint.g,
38195
+ tint.b,
38196
+ 1
38197
+ ], 8);
38198
+ data.set([
38199
+ fog.start,
38200
+ fog.end,
38201
+ fog.opacity ?? 1,
38202
+ 0
38203
+ ], 12);
38204
+ }
38205
+ const projection = Camera3D.computeProjectionMatrix(camera);
38206
+ data.set([
38207
+ 1 / projection[0],
38208
+ 1 / projection[5],
38209
+ camera.far,
38210
+ 0
38211
+ ], 16);
38212
+ const bindGroup = this.device.createBindGroup({
38213
+ layout: this.layout,
38214
+ entries: [
38215
+ {
38216
+ binding: 0,
38217
+ resource: { buffer: this.pool.getBuffer(this.device, data) }
38218
+ },
38219
+ {
38220
+ binding: 1,
38221
+ resource: source.createView()
38222
+ },
38223
+ {
38224
+ binding: 2,
38225
+ resource: geometry.createView()
38226
+ }
38227
+ ]
38228
+ });
38229
+ const pass = encoder.beginRenderPass({ colorAttachments: [this.attachment(source, dest)] });
38230
+ pass.setPipeline(this.pipeline);
38231
+ pass.setBindGroup(0, bindGroup);
38232
+ pass.draw(3);
38233
+ pass.end();
38234
+ }
38235
+ };
38236
+ /**
38237
+ * Color MSAA resolves average samples. Depth and octahedral normals cannot
38238
+ * be averaged across silhouettes: that invents a distant surface and a
38239
+ * normal that belongs to neither object. Keep the nearest visible sample
38240
+ * and carry outline coverage separately, without re-rendering geometry.
38241
+ */
38242
+ const shader = `
38243
+ @group(0) @binding(0) var geometry : texture_multisampled_2d<f32>;
38244
+ @vertex fn vs(@builtin(vertex_index) i : u32) -> @builtin(position) vec4<f32> {
38245
+ let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
38246
+ return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
38247
+ }
38248
+ @fragment fn fs(@builtin(position) pos : vec4<f32>) -> @location(0) vec4<f32> {
38249
+ let pixel = vec2<i32>(pos.xy);
38250
+ let samples = i32(textureNumSamples(geometry));
38251
+ var nearest = textureLoad(geometry, pixel, 0);
38252
+ var coverage = 0.0;
38253
+ for (var i = 0; i < samples; i++) {
38254
+ let s = textureLoad(geometry, pixel, i);
38255
+ if (s.r < nearest.r) { nearest = s; }
38256
+ coverage += s.a;
38257
+ }
38258
+ return vec4<f32>(nearest.rgb, nearest.a * coverage / f32(samples));
38259
+ }
38260
+ `;
38261
+ var GeometryResolvePipeline = class {
38262
+ layout;
38263
+ pipeline;
38264
+ constructor(device) {
38265
+ this.device = device;
38266
+ this.layout = device.createBindGroupLayout({ entries: [{
38267
+ binding: 0,
38268
+ visibility: GPUShaderStage.FRAGMENT,
38269
+ texture: {
38270
+ sampleType: "unfilterable-float",
38271
+ multisampled: true
38272
+ }
38273
+ }] });
38274
+ const module$1 = device.createShaderModule({
38275
+ label: "geometry-resolve.wgsl",
38276
+ code: shader
38277
+ });
38278
+ this.pipeline = device.createRenderPipeline({
38279
+ label: "GeometryResolve",
38280
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.layout] }),
38281
+ vertex: {
38282
+ module: module$1,
38283
+ entryPoint: "vs"
38284
+ },
38285
+ fragment: {
38286
+ module: module$1,
38287
+ entryPoint: "fs",
38288
+ targets: [{ format: "rgba16float" }]
38289
+ },
38290
+ primitive: { topology: "triangle-list" }
38291
+ });
38292
+ }
38293
+ render(encoder, source, dest) {
38294
+ const bindGroup = this.device.createBindGroup({
38295
+ layout: this.layout,
38296
+ entries: [{
38297
+ binding: 0,
38298
+ resource: source.createView()
38299
+ }]
38300
+ });
38301
+ const pass = encoder.beginRenderPass({ colorAttachments: [{
38302
+ view: dest,
38303
+ loadOp: "clear",
38304
+ storeOp: "store"
38305
+ }] });
38306
+ pass.setPipeline(this.pipeline);
38307
+ pass.setBindGroup(0, bindGroup);
38308
+ pass.draw(3);
38309
+ pass.end();
38310
+ }
38311
+ };
37789
38312
  function isKtx2(bytes) {
37790
38313
  return bytes.length >= KTX2_IDENTIFIER.length && KTX2_IDENTIFIER.every((byte, i) => bytes[i] === byte);
37791
38314
  }
@@ -38150,6 +38673,9 @@ struct TextModelUniforms {
38150
38673
  materialParams : vec4<f32>, // x = shininess, y = specularIntensity, z = ambientIntensity, w = materialMode (0=unlit, 1=lit)
38151
38674
  pbr : vec4<f32>, // x = roughness (<0 = default), y = metallic
38152
38675
  emissive : vec4<f32>, // rgb = sRGB emissive color, w = strength
38676
+ toonShadow : vec4<f32>, // rgb shadow-side multiplier, w diffuse modelling
38677
+ toonLight : vec4<f32>, // rgb lit-side multiplier, w terminator threshold
38678
+ toonParams : vec4<f32>, // x terminator softness, y outline participation
38153
38679
  };
38154
38680
 
38155
38681
  @group(0) @binding(0) var<uniform> cam : CameraUniforms;
@@ -38282,7 +38808,7 @@ fn shadeText(in : VSOut, fw : vec2<f32>) -> TextFragment {
38282
38808
  if (shaded.a < 0.0001) {
38283
38809
  return out;
38284
38810
  }
38285
- let lit = shadeSurfaceFull(in.worldPos, in.worldNormal, shaded.rgb / shaded.a, model.materialParams, model.pbr, model.emissive);
38811
+ let lit = shadeSurfaceFull(in.worldPos, in.worldNormal, shaded.rgb / shaded.a, model.materialParams, model.pbr, model.emissive, model.toonShadow, model.toonLight, model.toonParams);
38286
38812
  out.color = vec4<f32>(lit.display * shaded.a, shaded.a);
38287
38813
  out.hdr = vec4<f32>(lit.hdr * shaded.a, shaded.a);
38288
38814
  return out;
@@ -38323,7 +38849,7 @@ struct MrtBloomOutput {
38323
38849
  @fragment fn fs_mrt(in : VSOut) -> MrtOutput {
38324
38850
  var out : MrtOutput;
38325
38851
  out.color = textFragment(in).color;
38326
- out.depth = vec4<f32>(in.linearDepth, 0.0, 0.0, 1.0);
38852
+ out.depth = geometryPixel(in.linearDepth, in.worldNormal, model.toonParams.y);
38327
38853
  return out;
38328
38854
  }
38329
38855
 
@@ -38339,7 +38865,7 @@ struct MrtBloomOutput {
38339
38865
  let f = textFragment(in);
38340
38866
  var out : MrtBloomOutput;
38341
38867
  out.color = f.color;
38342
- out.depth = vec4<f32>(in.linearDepth, 0.0, 0.0, 1.0);
38868
+ out.depth = geometryPixel(in.linearDepth, in.worldNormal, model.toonParams.y);
38343
38869
  out.hdr = f.hdr;
38344
38870
  return out;
38345
38871
  }
@@ -39403,6 +39929,8 @@ var Renderer3D = class {
39403
39929
  shadowSampler = null;
39404
39930
  meshBounds = /* @__PURE__ */ new WeakMap();
39405
39931
  bloomPipeline = null;
39932
+ outlinePipeline = null;
39933
+ geometryResolvePipeline = null;
39406
39934
  lastCameraBuffer = null;
39407
39935
  /**
39408
39936
  * Samples per pixel in the 3D pass. Mesh silhouettes (extruded text,
@@ -39469,7 +39997,39 @@ var Renderer3D = class {
39469
39997
  primitive: { topology: "triangle-list" }
39470
39998
  });
39471
39999
  }
40000
+ /**
40001
+ * Size-dependent render targets, by label, size, samples and usage. One
40002
+ * frame (one command buffer) can render 3D scenes of different sizes, so
40003
+ * a target is never destroyed while the frame may use it: targets the last
40004
+ * frame did not use are destroyed at the next `resetPools`.
40005
+ */
40006
+ attachments = /* @__PURE__ */ new Map();
40007
+ attachmentsUsed = /* @__PURE__ */ new Set();
40008
+ attachment(label, width, height, desc) {
40009
+ const key = `${label}:${width}x${height}:${desc.sampleCount ?? 1}:${desc.usage}`;
40010
+ this.attachmentsUsed.add(key);
40011
+ let texture = this.attachments.get(key);
40012
+ if (!texture) {
40013
+ texture = this.device.createTexture({
40014
+ label,
40015
+ size: {
40016
+ width,
40017
+ height
40018
+ },
40019
+ ...desc
40020
+ });
40021
+ this.attachments.set(key, texture);
40022
+ }
40023
+ return texture;
40024
+ }
39472
40025
  resetPools() {
40026
+ for (const [key, texture] of this.attachments) {
40027
+ if (this.attachmentsUsed.has(key)) continue;
40028
+ texture.destroy();
40029
+ this.attachments.delete(key);
40030
+ }
40031
+ this.attachmentsUsed.clear();
40032
+ this.outlinePipeline?.reset();
39473
40033
  this.bloomPipeline?.resetPools();
39474
40034
  this.cameraPool.reset();
39475
40035
  this.lastCameraPose = null;
@@ -39484,33 +40044,18 @@ var Renderer3D = class {
39484
40044
  this.morphPipeline.resetPools();
39485
40045
  }
39486
40046
  getOrCreateDepthTexture(width, height) {
39487
- if (!this.depthTexture || this.depthTexture.width !== width || this.depthTexture.height !== height) {
39488
- this.depthTexture?.destroy();
39489
- this.depthTexture = this.device.createTexture({
39490
- label: "Renderer3DDepthTexture",
39491
- size: {
39492
- width,
39493
- height
39494
- },
39495
- format: "depth24plus",
39496
- sampleCount: this.sampleCount,
39497
- usage: GPUTextureUsage.RENDER_ATTACHMENT
39498
- });
39499
- }
40047
+ this.depthTexture = this.attachment("Renderer3DDepthTexture", width, height, {
40048
+ format: "depth24plus",
40049
+ sampleCount: this.sampleCount,
40050
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
40051
+ });
39500
40052
  return this.depthTexture;
39501
40053
  }
39502
- getOrCreateMsaaTexture(current, label, format, width, height) {
39503
- if (current && current.width === width && current.height === height) return current;
39504
- current?.destroy();
39505
- return this.device.createTexture({
39506
- label,
39507
- size: {
39508
- width,
39509
- height
39510
- },
40054
+ getOrCreateMsaaTexture(_current, label, format, width, height, sampled = false) {
40055
+ return this.attachment(label, width, height, {
39511
40056
  format,
39512
40057
  sampleCount: this.sampleCount,
39513
- usage: GPUTextureUsage.RENDER_ATTACHMENT
40058
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | (sampled ? GPUTextureUsage.TEXTURE_BINDING : 0)
39514
40059
  });
39515
40060
  }
39516
40061
  /** The HDR radiance target the last `beginPass(…, writeHdr)` drew into. */
@@ -39518,110 +40063,54 @@ var Renderer3D = class {
39518
40063
  return this.hdrTexture;
39519
40064
  }
39520
40065
  getOrCreateHdrTexture(width, height) {
39521
- if (!this.hdrTexture || this.hdrTexture.width !== width || this.hdrTexture.height !== height) {
39522
- this.hdrTexture?.destroy();
39523
- this.hdrTexture = this.device.createTexture({
39524
- label: "Renderer3DHdrRadiance",
39525
- size: {
39526
- width,
39527
- height
39528
- },
39529
- format: "rgba16float",
39530
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
39531
- });
39532
- }
40066
+ this.hdrTexture = this.attachment("Renderer3DHdrRadiance", width, height, {
40067
+ format: "rgba16float",
40068
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
40069
+ });
39533
40070
  return this.hdrTexture;
39534
40071
  }
39535
40072
  getOrCreateLinearDepthTexture(width, height) {
39536
- if (!this.linearDepthTexture || this.linearDepthTexture.width !== width || this.linearDepthTexture.height !== height) {
39537
- this.linearDepthTexture?.destroy();
39538
- this.linearDepthTexture = this.device.createTexture({
39539
- label: "Renderer3DLinearDepthTexture",
39540
- size: {
39541
- width,
39542
- height
39543
- },
39544
- format: "rgba16float",
39545
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
39546
- });
39547
- }
40073
+ this.linearDepthTexture = this.attachment("Renderer3DLinearDepthTexture", width, height, {
40074
+ format: "rgba16float",
40075
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
40076
+ });
39548
40077
  return this.linearDepthTexture;
39549
40078
  }
39550
40079
  getOrCreateReflectionTexture(width, height) {
39551
- if (!this.reflectionColorTexture || this.reflectionColorTexture.width !== width || this.reflectionColorTexture.height !== height) {
39552
- this.reflectionColorTexture?.destroy();
39553
- this.reflectionColorTexture = this.device.createTexture({
39554
- label: "Renderer3DReflectionColor",
39555
- size: {
39556
- width,
39557
- height
39558
- },
39559
- format: this.format,
39560
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
39561
- });
39562
- }
40080
+ this.reflectionColorTexture = this.attachment("Renderer3DReflectionColor", width, height, {
40081
+ format: this.format,
40082
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
40083
+ });
39563
40084
  return this.reflectionColorTexture;
39564
40085
  }
39565
40086
  getOrCreateReflectionMsaaTexture(width, height) {
39566
- if (!this.reflectionMsaaColorTexture || this.reflectionMsaaColorTexture.width !== width || this.reflectionMsaaColorTexture.height !== height) {
39567
- this.reflectionMsaaColorTexture?.destroy();
39568
- this.reflectionMsaaColorTexture = this.device.createTexture({
39569
- label: "Renderer3DReflectionMsaaColor",
39570
- size: {
39571
- width,
39572
- height
39573
- },
39574
- format: this.format,
39575
- sampleCount: this.sampleCount,
39576
- usage: GPUTextureUsage.RENDER_ATTACHMENT
39577
- });
39578
- }
40087
+ this.reflectionMsaaColorTexture = this.attachment("Renderer3DReflectionMsaaColor", width, height, {
40088
+ format: this.format,
40089
+ sampleCount: this.sampleCount,
40090
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
40091
+ });
39579
40092
  return this.reflectionMsaaColorTexture;
39580
40093
  }
39581
40094
  getOrCreateReflectionDepthTexture(width, height) {
39582
- if (!this.reflectionDepthTexture || this.reflectionDepthTexture.width !== width || this.reflectionDepthTexture.height !== height) {
39583
- this.reflectionDepthTexture?.destroy();
39584
- this.reflectionDepthTexture = this.device.createTexture({
39585
- label: "Renderer3DReflectionDepth",
39586
- size: {
39587
- width,
39588
- height
39589
- },
39590
- format: "depth24plus",
39591
- sampleCount: this.sampleCount,
39592
- usage: GPUTextureUsage.RENDER_ATTACHMENT
39593
- });
39594
- }
40095
+ this.reflectionDepthTexture = this.attachment("Renderer3DReflectionDepth", width, height, {
40096
+ format: "depth24plus",
40097
+ sampleCount: this.sampleCount,
40098
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
40099
+ });
39595
40100
  return this.reflectionDepthTexture;
39596
40101
  }
39597
40102
  getOrCreateReflectionTempTexture(width, height) {
39598
- if (!this.reflectionTempTexture || this.reflectionTempTexture.width !== width || this.reflectionTempTexture.height !== height) {
39599
- this.reflectionTempTexture?.destroy();
39600
- this.reflectionTempTexture = this.device.createTexture({
39601
- label: "Renderer3DReflectionTemp",
39602
- size: {
39603
- width,
39604
- height
39605
- },
39606
- format: this.format,
39607
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
39608
- });
39609
- }
40103
+ this.reflectionTempTexture = this.attachment("Renderer3DReflectionTemp", width, height, {
40104
+ format: this.format,
40105
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
40106
+ });
39610
40107
  return this.reflectionTempTexture;
39611
40108
  }
39612
40109
  getOrCreateReflectionBlurredTexture(width, height) {
39613
- if (!this.reflectionBlurredTexture || this.reflectionBlurredTexture.width !== width || this.reflectionBlurredTexture.height !== height) {
39614
- this.reflectionBlurredTexture?.destroy();
39615
- this.reflectionBlurredTexture = this.device.createTexture({
39616
- label: "Renderer3DReflectionBlurred",
39617
- size: {
39618
- width,
39619
- height
39620
- },
39621
- format: this.format,
39622
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
39623
- });
39624
- }
40110
+ this.reflectionBlurredTexture = this.attachment("Renderer3DReflectionBlurred", width, height, {
40111
+ format: this.format,
40112
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
40113
+ });
39625
40114
  return this.reflectionBlurredTexture;
39626
40115
  }
39627
40116
  packCameraUniforms(camera, surfaceWidth, surfaceHeight, time$2 = 0) {
@@ -39903,16 +40392,22 @@ var Renderer3D = class {
39903
40392
  storeOp: "store",
39904
40393
  clearValue
39905
40394
  };
39906
- if (msaa) this.msaaColorTexture = this.getOrCreateMsaaTexture(this.msaaColorTexture, "Renderer3DMsaaColor", this.format, surfaceWidth, surfaceHeight);
40395
+ if (msaa) this.msaaColorTexture = this.getOrCreateMsaaTexture(this.msaaColorTexture, "Renderer3DMsaaColor", this.format, surfaceWidth, surfaceHeight, Boolean(world?.environment?.outlines));
39907
40396
  const colorAttachments = [attach(colorTargetView, msaa ? this.msaaColorTexture : null, clearColor, loadOp)];
40397
+ if (msaa && world?.environment?.outlines) colorAttachments[0].storeOp = "store";
39908
40398
  if (linearDepthTargetView) {
39909
- if (msaa) this.msaaLinearDepthTexture = this.getOrCreateMsaaTexture(this.msaaLinearDepthTexture, "Renderer3DMsaaLinearDepth", "rgba16float", surfaceWidth, surfaceHeight);
39910
- colorAttachments.push(attach(linearDepthTargetView, msaa ? this.msaaLinearDepthTexture : null, {
39911
- r: camera.far,
39912
- g: 0,
39913
- b: 0,
39914
- a: 1
39915
- }, "clear"));
40399
+ if (msaa) this.msaaLinearDepthTexture = this.getOrCreateMsaaTexture(this.msaaLinearDepthTexture, "Renderer3DMsaaLinearDepth", "rgba16float", surfaceWidth, surfaceHeight, true);
40400
+ const geometryAttachment = attach(linearDepthTargetView, msaa ? this.msaaLinearDepthTexture : null, {
40401
+ r: Math.min(camera.far, 65504),
40402
+ g: .5,
40403
+ b: .5,
40404
+ a: 0
40405
+ }, "clear");
40406
+ if (msaa && world?.environment?.outlines) {
40407
+ geometryAttachment.resolveTarget = void 0;
40408
+ geometryAttachment.storeOp = "store";
40409
+ }
40410
+ colorAttachments.push(geometryAttachment);
39916
40411
  }
39917
40412
  this.passWritesHdr = writeHdr;
39918
40413
  if (writeHdr) {
@@ -40003,8 +40498,9 @@ var Renderer3D = class {
40003
40498
  this.floorReflectionPipeline.draw(pass, cameraBindGroup, lightsBindGroup, reflectionTexture, floor, useMrt, useBloom);
40004
40499
  }
40005
40500
  /**
40006
- * A layer's unit quad ([-0.5, 0.5] square) in world space:
40007
- * T(pos) * Rz * Ry * Rx * S(w * sx, h * sy, sz) * T(0.5 - ax, 0.5 - ay, 0).
40501
+ * A centered unit quad in world space. Translate to the authored pivot,
40502
+ * rotate and scale, then offset the unit quad back from that pivot.
40503
+ * Shared with children in preserve-3d containers.
40008
40504
  */
40009
40505
  quadModelMatrix(opts) {
40010
40506
  if (opts.modelMatrix) return opts.modelMatrix;
@@ -40018,7 +40514,7 @@ var Renderer3D = class {
40018
40514
  1
40019
40515
  ], anchor = [.5, .5] } = opts;
40020
40516
  let m = Matrix4Math.identity();
40021
- m = Matrix4Math.multiply(m, Matrix4Math.translate(position[0], position[1], position[2]));
40517
+ m = Matrix4Math.multiply(m, Matrix4Math.translate(position[0] + (anchor[0] - .5) * width, position[1] + (anchor[1] - .5) * height, position[2]));
40022
40518
  const rxRad = rotation[0] * Math.PI / 180;
40023
40519
  const ryRad = rotation[1] * Math.PI / 180;
40024
40520
  const rzRad = rotation[2] * Math.PI / 180;
@@ -40083,6 +40579,8 @@ var Renderer3D = class {
40083
40579
  metallic: opts.metallic,
40084
40580
  emissive: opts.emissive,
40085
40581
  emissiveIntensity: opts.emissiveIntensity,
40582
+ toon: opts.toon,
40583
+ outline: opts.outline,
40086
40584
  matte: opts.matte
40087
40585
  }, useMrt, this.passWritesHdr);
40088
40586
  }
@@ -40128,6 +40626,8 @@ var Renderer3D = class {
40128
40626
  metallic,
40129
40627
  emissive: opts.emissive,
40130
40628
  emissiveIntensity: opts.emissiveIntensity,
40629
+ toon: opts.toon,
40630
+ outline: opts.outline,
40131
40631
  matte: opts.matte,
40132
40632
  relief: opts.relief
40133
40633
  }, useMrt, this.passWritesHdr);
@@ -40371,6 +40871,8 @@ var Renderer3D = class {
40371
40871
  metallic,
40372
40872
  emissive: opts.emissive,
40373
40873
  emissiveIntensity: opts.emissiveIntensity,
40874
+ toon: opts.toon,
40875
+ outline: opts.outline,
40374
40876
  matte: opts.matte,
40375
40877
  maps: opts.maps,
40376
40878
  isInstanced: Boolean(opts.instanceBuffer || opts.instanceCount && opts.instanceCount > 1),
@@ -40525,6 +41027,23 @@ var Renderer3D = class {
40525
41027
  this.bloomPipeline ??= new BloomPipeline(this.device);
40526
41028
  this.bloomPipeline.apply(encoder, source, dest, options, hdrSource ?? void 0);
40527
41029
  }
41030
+ /** Geometry ink is applied before lens blur, independently of material shading. */
41031
+ resolveGeometry(encoder, geometry) {
41032
+ if (this.sampleCount <= 1 || !this.msaaLinearDepthTexture) return;
41033
+ this.geometryResolvePipeline ??= new GeometryResolvePipeline(this.device);
41034
+ this.geometryResolvePipeline.render(encoder, this.msaaLinearDepthTexture, geometry.createView());
41035
+ }
41036
+ applyOutlines(encoder, source, geometry, dest, camera, options, fog) {
41037
+ let colorSource = source;
41038
+ let geometrySource = geometry;
41039
+ if (this.sampleCount > 1) {
41040
+ if (!this.msaaColorTexture || !this.msaaLinearDepthTexture) throw new Error("Geometry outlines require the scene's retained MSAA color and geometry samples");
41041
+ colorSource = this.msaaColorTexture;
41042
+ geometrySource = this.msaaLinearDepthTexture;
41043
+ }
41044
+ this.outlinePipeline ??= new GeometryOutlinePipeline(this.device, this.format, this.sampleCount);
41045
+ this.outlinePipeline.render(encoder, colorSource, geometrySource, dest, camera, options, fog);
41046
+ }
40528
41047
  applyDepthOfField(encoder, sourceColorTex, depthTex, destView, cameraBuffer) {
40529
41048
  const sampler = this.samplerCache.getSampler(this.device);
40530
41049
  const bindGroup = this.device.createBindGroup({
@@ -40577,7 +41096,10 @@ var Renderer3D = class {
40577
41096
  }, useMrt);
40578
41097
  }
40579
41098
  destroy() {
41099
+ for (const texture of this.attachments.values()) texture.destroy();
41100
+ this.attachments.clear();
40580
41101
  this.bloomPipeline?.destroy();
41102
+ this.outlinePipeline?.destroy();
40581
41103
  this.depthTexture?.destroy();
40582
41104
  for (const texture of this.shadowTextures.values()) texture.destroy();
40583
41105
  this.shadowTextures.clear();
@@ -41608,5 +42130,5 @@ var NodeSurfaceProvider = class {
41608
42130
  if (typeof globalThis !== "undefined") globalThis.__GATEWAI_DELAYS__ = globalThis.__GATEWAI_DELAYS__ || /* @__PURE__ */ new Set();
41609
42131
 
41610
42132
  //#endregion
41611
- export { probeVideoSize as $, TextAnimatorBuilder as $t, emojiGraphemes as A, evaluateSpringProgress as An, AudioSignalComputePipeline as At, jointMatrices as B, samplePathChainGeometry as Bn, KineticMarqueeConfigSchema as Bt, drawGifNode as C, ellipseToCubicBeziers as Cn, resolveEnvironment as Ct, drawSignalNode as D, evaluateMarqueeOffset as Dn, AnchorGroupingSchema as Dt, drawParagraphNode as E, evaluateExpressionSelector as En, AdvancedTextAnimatorSchema as Et, getPrimitiveGeometry as F, getScramblePool as Fn, EnhancedTextNodeSchema as Ft, measureMaxCaptionHeight as G, SelectorModeSchema as Gt, loadModel3D as H, waveToCubicBeziers as Hn, LongShadowConfigSchema as Ht, getRegisteredFontSource as I, getShuffledOrder as In, ExpressionSelectorSchema as It, parseFBX as J, SignalRegistry as Jt, mediaDecoderCache as K, SelectorShapeSchema as Kt, getRenderer2D as L, parseSvgPathToCubicBeziers as Ln, ExtendedGlyphPropertiesSchema as Lt, fitSpansToText as M, evaluateVolumetricFormation as Mn, ChromaticGlitchConfigSchema as Mt, generateExtrudedTextGeometry as N, generateArcLengthLUT as Nn, CubicBezierSegmentSchema as Nt, drawSvgNode as O, evaluateSelectorCombination as On, AnchorPresetSchema as Ot, getHeadlessFontPath as P, generatePathSegments as Pn, DynamicStrokePropertiesSchema as Pt, poseNodes as Q, TextAnchorConfigSchema as Qt, initHeadlessWebGPU as R, sampleAudioSpectrumScale as Rn, GlyphAnchorGroupingSchema as Rt, drawCaptionNode as S, distanceToCurveParameter as Sn, parseColorToRgb as St, drawLottieNode as T, evaluateCubicBezierDerivative as Tn, AdvancedTextAnimatorBuilder as Tt, loadOBJ as U, ParticleDissolveConfigSchema as Ut, loadFBX as V, signalRegistry as Vn, LineSpacingDynamicsSchema as Vt, lutStore as W, SelectorBasedOnSchema as Wt, parseOBJ as X, Spatial3DPropertiesSchema as Xt, parseMTL as Y, SkewPropertiesSchema as Yt, parseSvgPathToSegments as Z, SpringPhysicsSchema as Zt, buildShapeSvgPath as _, buildWGSLSignalFn as _n, MAX_LIGHTS as _t, GlassPipeline as a, TextRangeSelectorSchema as an, shaderStore as at, colorMatrixWgsl as b, computeRangeSelectorWeight as bn, Vector3Math as bt, Renderer2D as c, TextSignalSelectorSchema as cn, textureCache as ct, ScreenSpaceRelightPipeline as d, TypingCadenceSchema as dn, GetFontAssetUrl as dt, TextCursorConfigSchema as en, registerHeadlessFont as et, ShadowPipeline as f, VariableFontAxesSchema as fn, resolveMediaSourceUrl as ft, WebGPUAudioProcessor as g, buildPathLUT as gn, Light3D as gt, SlugPipeline as h, VolumetricFormationModeSchema as hn, LIGHT_UNIFORM_SIZE_BYTES as ht, EffectPipeline as i, TextPathSourceSchema as in, setRegisteredFontSource as it, ensureDevice as j, evaluateTypewriterState as jn, AudioSpectrumTypographySchema as jt, drawVideoNode as k, evaluateSkewMatrix as kn, AnimatableGlyphPropsSchema as kt, Renderer3D as l, TextWigglySelectorSchema as ln, trackAudioPitch as lt, SlugGeometry as m, VolumetricFormationConfigSchema as mn, ENVIRONMENT_PRESETS as mt, AudioLatentTrackCache as n, TextPathBuilder as nn, resolveTextSpans as nt, MotionBlurPipeline as o, TextScrambleConfigSchema as on, spansText as ot, SlugFontCache as p, Vec2Schema as pn, Camera3D as pt, parseColor as q, SelectorUnitsSchema as qt, AudioMeshDeformPipeline as r, TextPathOptionsSchema as rn, sampleMorphWeights as rt, NodeSurfaceProvider as s, TextSelectorSchema as sn, srtLoader as st, AudioLatentEngine as t, TextCursorStyleSchema as tn, resolveInputUrl as tt, SSAOPipeline as u, TypewriterAnimatorSchema as un, transformScale as ut, clearAllVideoCache as v, computeAnchorPivotOffset as vn, Matrix4Math as vt, drawImageNode as w, evaluateCubicBezier as wn, AETextAnimatorSchema as wt, describeGpu as x, deterministicWigglyNoise as xn, linearRgb as xt, clearHeadlessFonts as y, computeLineLeadingDisplacement as yn, Path3D as yt, inputStore as z, sampleCurveGeometry as zn, GlyphAnchorPointSchema as zt };
41612
- //# sourceMappingURL=dist-D3BeAZ7X.mjs.map
42133
+ export { poseNodes as $, TextAnchorConfigSchema as $t, emojiGraphemes as A, evaluateSkewMatrix as An, AnimatableGlyphPropsSchema as At, jointMatrices as B, sampleCurveGeometry as Bn, GlyphAnchorPointSchema as Bt, drawGifNode as C, distanceToCurveParameter as Cn, parseColorToRgb as Ct, drawSignalNode as D, evaluateExpressionSelector as Dn, AdvancedTextAnimatorSchema as Dt, drawParagraphNode as E, evaluateCubicBezierDerivative as En, AdvancedTextAnimatorBuilder as Et, getPrimitiveGeometry as F, generatePathSegments as Fn, DynamicStrokePropertiesSchema as Ft, measureMaxCaptionHeight as G, SelectorBasedOnSchema as Gt, loadModel3D as H, signalRegistry as Hn, LineSpacingDynamicsSchema as Ht, getRegisteredFontSource as I, getScramblePool as In, EnhancedTextNodeSchema as It, parseDashArray as J, SelectorUnitsSchema as Jt, mediaDecoderCache as K, SelectorModeSchema as Kt, getRenderer2D as L, getShuffledOrder as Ln, ExpressionSelectorSchema as Lt, fitSpansToText as M, evaluateTypewriterState as Mn, AudioSpectrumTypographySchema as Mt, generateExtrudedTextGeometry as N, evaluateVolumetricFormation as Nn, ChromaticGlitchConfigSchema as Nt, drawSvgNode as O, evaluateMarqueeOffset as On, AnchorGroupingSchema as Ot, getHeadlessFontPath as P, generateArcLengthLUT as Pn, CubicBezierSegmentSchema as Pt, parseSvgPathToSegments as Q, SpringPhysicsSchema as Qt, initHeadlessWebGPU as R, parseSvgPathToCubicBeziers as Rn, ExtendedGlyphPropertiesSchema as Rt, drawCaptionNode as S, deterministicWigglyNoise as Sn, linearRgb as St, drawLottieNode as T, evaluateCubicBezier as Tn, AETextAnimatorSchema as Tt, loadOBJ as U, waveToCubicBeziers as Un, LongShadowConfigSchema as Ut, loadFBX as V, samplePathChainGeometry as Vn, KineticMarqueeConfigSchema as Vt, lutStore as W, ParticleDissolveConfigSchema as Wt, parseMTL as X, SkewPropertiesSchema as Xt, parseFBX as Y, SignalRegistry as Yt, parseOBJ as Z, Spatial3DPropertiesSchema as Zt, buildShapeSvgPath as _, buildPathLUT as _n, Light3D as _t, GlassPipeline as a, TextPathSourceSchema as an, setRegisteredFontSource as at, colorMatrixWgsl as b, computeLineLeadingDisplacement as bn, Path3D as bt, Renderer2D as c, TextSelectorSchema as cn, srtLoader as ct, ScreenSpaceRelightPipeline as d, TypewriterAnimatorSchema as dn, transformScale as dt, TextAnimatorBuilder as en, probeVideoSize as et, ShadowPipeline as f, TypingCadenceSchema as fn, GetFontAssetUrl as ft, WebGPUAudioProcessor as g, VolumetricFormationModeSchema as gn, LIGHT_UNIFORM_SIZE_BYTES as gt, SlugPipeline as h, VolumetricFormationConfigSchema as hn, ENVIRONMENT_PRESETS as ht, EffectPipeline as i, TextPathOptionsSchema as in, sampleMorphWeights as it, ensureDevice as j, evaluateSpringProgress as jn, AudioSignalComputePipeline as jt, drawVideoNode as k, evaluateSelectorCombination as kn, AnchorPresetSchema as kt, Renderer3D as l, TextSignalSelectorSchema as ln, textureCache as lt, SlugGeometry as m, Vec2Schema as mn, Camera3D as mt, AudioLatentTrackCache as n, TextCursorStyleSchema as nn, resolveInputUrl as nt, MotionBlurPipeline as o, TextRangeSelectorSchema as on, shaderStore as ot, SlugFontCache as p, VariableFontAxesSchema as pn, resolveMediaSourceUrl as pt, parseColor as q, SelectorShapeSchema as qt, AudioMeshDeformPipeline as r, TextPathBuilder as rn, resolveTextSpans as rt, NodeSurfaceProvider as s, TextScrambleConfigSchema as sn, spansText as st, AudioLatentEngine as t, TextCursorConfigSchema as tn, registerHeadlessFont as tt, SSAOPipeline as u, TextWigglySelectorSchema as un, trackAudioPitch as ut, clearAllVideoCache as v, buildWGSLSignalFn as vn, MAX_LIGHTS as vt, drawImageNode as w, ellipseToCubicBeziers as wn, resolveEnvironment as wt, describeGpu as x, computeRangeSelectorWeight as xn, Vector3Math as xt, clearHeadlessFonts as y, computeAnchorPivotOffset as yn, Matrix4Math as yt, inputStore as z, sampleAudioSpectrumScale as zn, GlyphAnchorGroupingSchema as zt };
42134
+ //# sourceMappingURL=dist--Uu5bQ_6.mjs.map