framefields 2.8.0 → 2.8.1

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 (285) hide show
  1. package/dist/audio/index.d.mts +4 -4
  2. package/dist/audio/index.mjs +10 -10
  3. package/dist/{audio-CxAUxVyA.d.mts → audio-Ccpl9mjV.d.mts} +3 -3
  4. package/dist/{audio-CxAUxVyA.d.mts.map → audio-Ccpl9mjV.d.mts.map} +1 -1
  5. package/dist/{audio-CGVJlZb9.mjs → audio-vatRe308.mjs} +3 -3
  6. package/dist/{audio-CGVJlZb9.mjs.map → audio-vatRe308.mjs.map} +1 -1
  7. package/dist/cli/check-worker.mjs +43 -43
  8. package/dist/components/index.d.mts +10 -10
  9. package/dist/components/index.mjs +42 -42
  10. package/dist/{dist-C5eZw8ms.mjs → dist-BbwtlSZO.mjs} +2 -2
  11. package/dist/{dist-C5eZw8ms.mjs.map → dist-BbwtlSZO.mjs.map} +1 -1
  12. package/dist/{dist-CnFcSau0.mjs → dist-CFe1fC9l.mjs} +2289 -212
  13. package/dist/dist-CFe1fC9l.mjs.map +1 -0
  14. package/dist/effects/index.d.mts +4 -4
  15. package/dist/effects/index.mjs +34 -34
  16. package/dist/{effects-DdvkiI3u.mjs → effects-CCyfjbhZ.mjs} +2 -2
  17. package/dist/{effects-DdvkiI3u.mjs.map → effects-CCyfjbhZ.mjs.map} +1 -1
  18. package/dist/fonts/index.mjs +2 -2
  19. package/dist/{fonts-k_89s1HO.mjs → fonts-C1mgtkCa.mjs} +2 -2
  20. package/dist/{fonts-k_89s1HO.mjs.map → fonts-C1mgtkCa.mjs.map} +1 -1
  21. package/dist/{gpu-BoyykRdD.mjs → gpu-CU2tEKJJ.mjs} +2 -2
  22. package/dist/{gpu-BoyykRdD.mjs.map → gpu-CU2tEKJJ.mjs.map} +1 -1
  23. package/dist/{index-CPFKis94.d.mts → index-7Z9_a_iB.d.mts} +3 -3
  24. package/dist/{index-CPFKis94.d.mts.map → index-7Z9_a_iB.d.mts.map} +1 -1
  25. package/dist/{index-CFKMZDv1.d.mts → index-BIjMAW1h.d.mts} +2 -2
  26. package/dist/{index-CFKMZDv1.d.mts.map → index-BIjMAW1h.d.mts.map} +1 -1
  27. package/dist/{index-Ds9_v60R.d.mts → index-BwRrsP_V.d.mts} +181 -13
  28. package/dist/index-BwRrsP_V.d.mts.map +1 -0
  29. package/dist/{index-BukqOGDa.d.mts → index-CFoAmh7q.d.mts} +3 -3
  30. package/dist/{index-BukqOGDa.d.mts.map → index-CFoAmh7q.d.mts.map} +1 -1
  31. package/dist/{index-CPeaSZoz.d.mts → index-CJL0JAU8.d.mts} +2 -2
  32. package/dist/{index-CPeaSZoz.d.mts.map → index-CJL0JAU8.d.mts.map} +1 -1
  33. package/dist/{index-Ced1Ry4S.d.mts → index-D5UDyVMb.d.mts} +211 -9
  34. package/dist/index-D5UDyVMb.d.mts.map +1 -0
  35. package/dist/{index-B_nooAhS.d.mts → index-UDDAfHAF.d.mts} +4 -4
  36. package/dist/{index-B_nooAhS.d.mts.map → index-UDDAfHAF.d.mts.map} +1 -1
  37. package/dist/index.d.mts +11 -11
  38. package/dist/index.mjs +49 -49
  39. package/dist/preview/index.d.mts +3 -3
  40. package/dist/preview/index.mjs +10 -10
  41. package/dist/{preview-DHqCrFhG.mjs → preview-WXbsf6Dv.mjs} +2 -2
  42. package/dist/{preview-DHqCrFhG.mjs.map → preview-WXbsf6Dv.mjs.map} +1 -1
  43. package/dist/preview-engine/{chunk-K5F4ZW6T.mjs → chunk-7U34VKNO.mjs} +1 -1
  44. package/dist/preview-engine/chunk-FKG27DVO.mjs +4 -0
  45. package/dist/preview-engine/chunk-KFNVUGNN.mjs +1974 -0
  46. package/dist/preview-engine/chunk-NKAVHU7H.mjs +480 -0
  47. package/dist/preview-engine/chunk-PYX6Q2EJ.mjs +2403 -0
  48. package/dist/preview-engine/components.mjs +1 -1
  49. package/dist/preview-engine/framefields.mjs +1 -1
  50. package/dist/preview-engine/player.mjs +1 -1
  51. package/dist/preview-engine/renderers-KPCQGBIY.mjs +228 -0
  52. package/dist/{program-CqQDWfSV.mjs → program-Cq4gP9Dd.mjs} +5 -6
  53. package/dist/program-Cq4gP9Dd.mjs.map +1 -0
  54. package/dist/renderer/index.d.mts +2 -2
  55. package/dist/renderer/index.mjs +2 -2
  56. package/dist/renderer-01zxT4CK.mjs +5 -0
  57. package/dist/renderer-35LHkj36.mjs +5 -0
  58. package/dist/{renderer-Cx2Sw9Ib.mjs → renderer-AMBVtZ_G.mjs} +3 -3
  59. package/dist/{renderer-Cx2Sw9Ib.mjs.map → renderer-AMBVtZ_G.mjs.map} +1 -1
  60. package/dist/renderer-B0OIdCGj.mjs +5 -0
  61. package/dist/renderer-B3IPtkK9.mjs +5 -0
  62. package/dist/{renderer-DCyp-jzQ.mjs → renderer-B4iCFvJh.mjs} +3 -3
  63. package/dist/{renderer-DCyp-jzQ.mjs.map → renderer-B4iCFvJh.mjs.map} +1 -1
  64. package/dist/{renderer-DsGE9qtz.mjs → renderer-BD00i68i.mjs} +3 -3
  65. package/dist/{renderer-DsGE9qtz.mjs.map → renderer-BD00i68i.mjs.map} +1 -1
  66. package/dist/{renderer-CuK1lPnA.mjs → renderer-BEuU7qlG.mjs} +2 -2
  67. package/dist/{renderer-CuK1lPnA.mjs.map → renderer-BEuU7qlG.mjs.map} +1 -1
  68. package/dist/{renderer-BuYRrBRf.mjs → renderer-BGPnCRFI.mjs} +3 -3
  69. package/dist/{renderer-BuYRrBRf.mjs.map → renderer-BGPnCRFI.mjs.map} +1 -1
  70. package/dist/{renderer-D7FuHXiz.mjs → renderer-BIGILDEA.mjs} +2 -2
  71. package/dist/{renderer-D7FuHXiz.mjs.map → renderer-BIGILDEA.mjs.map} +1 -1
  72. package/dist/{renderer-4GwADfB0.mjs → renderer-BLU0PQTB.mjs} +3 -3
  73. package/dist/{renderer-4GwADfB0.mjs.map → renderer-BLU0PQTB.mjs.map} +1 -1
  74. package/dist/renderer-BMC39abo.mjs +5 -0
  75. package/dist/{renderer-DS-Gv-3w.mjs → renderer-BNzT34UV.mjs} +2 -2
  76. package/dist/renderer-BQj3nlAI.mjs +5 -0
  77. package/dist/{renderer-DqMZiy5N.mjs → renderer-BWlMAacF.mjs} +2 -2
  78. package/dist/{renderer-DqMZiy5N.mjs.map → renderer-BWlMAacF.mjs.map} +1 -1
  79. package/dist/{renderer-DXu0nrzo.d.mts → renderer-BX7jS14r.d.mts} +2 -2
  80. package/dist/{renderer-DXu0nrzo.d.mts.map → renderer-BX7jS14r.d.mts.map} +1 -1
  81. package/dist/{renderer-DPUULdjh.mjs → renderer-BXAgUuAq.mjs} +2 -2
  82. package/dist/{renderer-DPUULdjh.mjs.map → renderer-BXAgUuAq.mjs.map} +1 -1
  83. package/dist/{renderer-Cn34qZC4.mjs → renderer-BZKkT-6Q.mjs} +2 -2
  84. package/dist/{renderer-Cn34qZC4.mjs.map → renderer-BZKkT-6Q.mjs.map} +1 -1
  85. package/dist/{renderer-BcHpcLq3.mjs → renderer-BdbGTz7z.mjs} +3 -3
  86. package/dist/{renderer-BcHpcLq3.mjs.map → renderer-BdbGTz7z.mjs.map} +1 -1
  87. package/dist/{renderer-Dhi0Q1cI.mjs → renderer-BeFiQfzV.mjs} +69 -69
  88. package/dist/{renderer-Dhi0Q1cI.mjs.map → renderer-BeFiQfzV.mjs.map} +1 -1
  89. package/dist/{renderer-kg8-ngnB.mjs → renderer-BeQrWrOa.mjs} +2 -2
  90. package/dist/{renderer-kg8-ngnB.mjs.map → renderer-BeQrWrOa.mjs.map} +1 -1
  91. package/dist/{renderer-Ca-LoeJh.mjs → renderer-BioUC9zE.mjs} +2 -2
  92. package/dist/{renderer-Ca-LoeJh.mjs.map → renderer-BioUC9zE.mjs.map} +1 -1
  93. package/dist/{renderer-wH_l-jMH.mjs → renderer-Bkqg-U2F.mjs} +3 -3
  94. package/dist/{renderer-wH_l-jMH.mjs.map → renderer-Bkqg-U2F.mjs.map} +1 -1
  95. package/dist/{renderer-Sj11W1xq.mjs → renderer-BlVmbzPs.mjs} +2 -2
  96. package/dist/renderer-BnBvSmp_.mjs +5 -0
  97. package/dist/{renderer-Qd-HCwKj.mjs → renderer-Br4UZvfL.mjs} +3 -3
  98. package/dist/{renderer-Qd-HCwKj.mjs.map → renderer-Br4UZvfL.mjs.map} +1 -1
  99. package/dist/{renderer-DX5ZpEBB.mjs → renderer-BrWOcJ9Q.mjs} +3 -3
  100. package/dist/{renderer-DX5ZpEBB.mjs.map → renderer-BrWOcJ9Q.mjs.map} +1 -1
  101. package/dist/renderer-BsDSpTpL.mjs +5 -0
  102. package/dist/renderer-BtW6_gvE.mjs +5 -0
  103. package/dist/renderer-By8U44Rt.mjs +5 -0
  104. package/dist/{renderer-XnWe1hSL.mjs → renderer-C-5RNIeX.mjs} +2 -2
  105. package/dist/{renderer-XnWe1hSL.mjs.map → renderer-C-5RNIeX.mjs.map} +1 -1
  106. package/dist/renderer-C3lDmxuX.mjs +5 -0
  107. package/dist/renderer-C6SZAE_x.mjs +5 -0
  108. package/dist/{renderer-DSM5c7UA.mjs → renderer-C8rxyjaE.mjs} +2 -2
  109. package/dist/{renderer-DSM5c7UA.mjs.map → renderer-C8rxyjaE.mjs.map} +1 -1
  110. package/dist/renderer-CA3IVeZj.mjs +5 -0
  111. package/dist/{renderer-DYT4iPi-.mjs → renderer-CAagaRag.mjs} +2 -2
  112. package/dist/{renderer-DYT4iPi-.mjs.map → renderer-CAagaRag.mjs.map} +1 -1
  113. package/dist/{renderer-CaE0rp9j.mjs → renderer-CCSzKnvs.mjs} +2 -2
  114. package/dist/{renderer-CaE0rp9j.mjs.map → renderer-CCSzKnvs.mjs.map} +1 -1
  115. package/dist/renderer-CFoCH_mO.mjs +5 -0
  116. package/dist/renderer-CLhZqJF8.mjs +6 -0
  117. package/dist/{renderer-Bw5jGUo5.mjs → renderer-COQc1jn6.mjs} +2 -2
  118. package/dist/{renderer-Bw5jGUo5.mjs.map → renderer-COQc1jn6.mjs.map} +1 -1
  119. package/dist/{renderer-DB7PM7nZ.mjs → renderer-CP2FOWyJ.mjs} +3 -3
  120. package/dist/{renderer-DB7PM7nZ.mjs.map → renderer-CP2FOWyJ.mjs.map} +1 -1
  121. package/dist/{renderer-DXsV3mZD.mjs → renderer-CUq2qgyc.mjs} +3 -3
  122. package/dist/{renderer-DXsV3mZD.mjs.map → renderer-CUq2qgyc.mjs.map} +1 -1
  123. package/dist/{renderer-DYfNgEhK.mjs → renderer-CXWAdRqn.mjs} +2 -2
  124. package/dist/{renderer-DYfNgEhK.mjs.map → renderer-CXWAdRqn.mjs.map} +1 -1
  125. package/dist/renderer-CYJXQS8E.mjs +5 -0
  126. package/dist/{renderer-JDLYPnRh.mjs → renderer-CZyJ72JZ.mjs} +2 -2
  127. package/dist/{renderer-JDLYPnRh.mjs.map → renderer-CZyJ72JZ.mjs.map} +1 -1
  128. package/dist/renderer-C_vj6_WJ.mjs +5 -0
  129. package/dist/{renderer-4F4FypvN.mjs → renderer-Ce8VPaog.mjs} +2 -2
  130. package/dist/{renderer-4F4FypvN.mjs.map → renderer-Ce8VPaog.mjs.map} +1 -1
  131. package/dist/{renderer-4a4koLCl.mjs → renderer-CeX7uUXB.mjs} +2 -2
  132. package/dist/{renderer-4a4koLCl.mjs.map → renderer-CeX7uUXB.mjs.map} +1 -1
  133. package/dist/renderer-CeqfC6OR.mjs +5 -0
  134. package/dist/{renderer-NvGahExL.mjs → renderer-Ci9x2WUd.mjs} +3 -3
  135. package/dist/{renderer-NvGahExL.mjs.map → renderer-Ci9x2WUd.mjs.map} +1 -1
  136. package/dist/{renderer-iA7rxZmR.mjs → renderer-CiMIftlS.mjs} +3 -3
  137. package/dist/{renderer-iA7rxZmR.mjs.map → renderer-CiMIftlS.mjs.map} +1 -1
  138. package/dist/{renderer-CpEU-TRR.mjs → renderer-CjklM-KD.mjs} +2 -2
  139. package/dist/{renderer-CpEU-TRR.mjs.map → renderer-CjklM-KD.mjs.map} +1 -1
  140. package/dist/{renderer-CNVzJIBz.mjs → renderer-Cjx3x7xM.mjs} +3 -3
  141. package/dist/{renderer-CNVzJIBz.mjs.map → renderer-Cjx3x7xM.mjs.map} +1 -1
  142. package/dist/renderer-CkZCGMAc.mjs +5 -0
  143. package/dist/renderer-Clxju1r9.mjs +5 -0
  144. package/dist/{renderer-l-K6iZLC.mjs → renderer-CrgfMDbh.mjs} +3 -3
  145. package/dist/{renderer-l-K6iZLC.mjs.map → renderer-CrgfMDbh.mjs.map} +1 -1
  146. package/dist/{renderer-fv9WvK0d.mjs → renderer-CsilJwL-.mjs} +3 -3
  147. package/dist/{renderer-fv9WvK0d.mjs.map → renderer-CsilJwL-.mjs.map} +1 -1
  148. package/dist/{renderer-DBN6SV8L.mjs → renderer-CxEBV-w0.mjs} +2 -2
  149. package/dist/{renderer-DBN6SV8L.mjs.map → renderer-CxEBV-w0.mjs.map} +1 -1
  150. package/dist/{renderer-Bw3tlr9I.mjs → renderer-CyWRvKUI.mjs} +2 -2
  151. package/dist/{renderer-Bw3tlr9I.mjs.map → renderer-CyWRvKUI.mjs.map} +1 -1
  152. package/dist/renderer-CyWtKBF7.mjs +5 -0
  153. package/dist/{renderer-BlUfAZyK.mjs → renderer-D-17HVPb.mjs} +3 -3
  154. package/dist/{renderer-BlUfAZyK.mjs.map → renderer-D-17HVPb.mjs.map} +1 -1
  155. package/dist/renderer-D27STGoB.mjs +5 -0
  156. package/dist/{renderer-BUV6IH0o.mjs → renderer-D2JOseb1.mjs} +2 -2
  157. package/dist/{renderer-BUV6IH0o.mjs.map → renderer-D2JOseb1.mjs.map} +1 -1
  158. package/dist/{renderer-BYIa_hnS.mjs → renderer-D3K9FEQk.mjs} +3 -3
  159. package/dist/{renderer-BYIa_hnS.mjs.map → renderer-D3K9FEQk.mjs.map} +1 -1
  160. package/dist/{renderer-bE6yZ_Ef.mjs → renderer-D41gHat4.mjs} +3 -3
  161. package/dist/{renderer-bE6yZ_Ef.mjs.map → renderer-D41gHat4.mjs.map} +1 -1
  162. package/dist/{renderer-C7SYbHgB.mjs → renderer-D6X9vWkN.mjs} +2 -2
  163. package/dist/{renderer-C7SYbHgB.mjs.map → renderer-D6X9vWkN.mjs.map} +1 -1
  164. package/dist/renderer-D78Aq9YK.mjs +5 -0
  165. package/dist/{renderer-ClxmV_id.mjs → renderer-D7Cu9OZx.mjs} +2 -2
  166. package/dist/{renderer-ClxmV_id.mjs.map → renderer-D7Cu9OZx.mjs.map} +1 -1
  167. package/dist/{renderer-DO15Nj-w.mjs → renderer-D7Kk7VxM.mjs} +3 -3
  168. package/dist/{renderer-DO15Nj-w.mjs.map → renderer-D7Kk7VxM.mjs.map} +1 -1
  169. package/dist/{renderer-DZKZup7n.mjs → renderer-D8CMakyz.mjs} +3 -3
  170. package/dist/{renderer-DZKZup7n.mjs.map → renderer-D8CMakyz.mjs.map} +1 -1
  171. package/dist/{renderer-DbO4xZBb.mjs → renderer-DEcbSNQd.mjs} +3 -3
  172. package/dist/{renderer-DbO4xZBb.mjs.map → renderer-DEcbSNQd.mjs.map} +1 -1
  173. package/dist/{renderer-DjWBBISJ.mjs → renderer-DGPaKIFf.mjs} +2 -2
  174. package/dist/renderer-DHEffABL.mjs +5 -0
  175. package/dist/{renderer-CTr_Zi08.mjs → renderer-DIPyUVX2.mjs} +3 -3
  176. package/dist/{renderer-CTr_Zi08.mjs.map → renderer-DIPyUVX2.mjs.map} +1 -1
  177. package/dist/{renderer-D_Mf-Zft.d.mts → renderer-DKgPVOLL.d.mts} +2 -2
  178. package/dist/{renderer-D_Mf-Zft.d.mts.map → renderer-DKgPVOLL.d.mts.map} +1 -1
  179. package/dist/{renderer-BrXkb-9N.mjs → renderer-DPn2DhMU.mjs} +2 -2
  180. package/dist/{renderer-BrXkb-9N.mjs.map → renderer-DPn2DhMU.mjs.map} +1 -1
  181. package/dist/{renderer-CyELq6P6.mjs → renderer-DRGa6tlX.mjs} +2 -2
  182. package/dist/{renderer-CyELq6P6.mjs.map → renderer-DRGa6tlX.mjs.map} +1 -1
  183. package/dist/{renderer-Crl8q_vD.mjs → renderer-DSpTGqUU.mjs} +3 -3
  184. package/dist/{renderer-Crl8q_vD.mjs.map → renderer-DSpTGqUU.mjs.map} +1 -1
  185. package/dist/{renderer-vVKNp3ri.mjs → renderer-DTgO5LPf.mjs} +3 -3
  186. package/dist/{renderer-vVKNp3ri.mjs.map → renderer-DTgO5LPf.mjs.map} +1 -1
  187. package/dist/renderer-DUuNCgzW.mjs +5 -0
  188. package/dist/renderer-DVD5pzRq.mjs +5 -0
  189. package/dist/{renderer-C55eLbl1.mjs → renderer-DgLt0TPo.mjs} +2 -2
  190. package/dist/{renderer-C55eLbl1.mjs.map → renderer-DgLt0TPo.mjs.map} +1 -1
  191. package/dist/{renderer-BHwbnT6S.mjs → renderer-Dk45W8Zd.mjs} +3 -3
  192. package/dist/{renderer-BHwbnT6S.mjs.map → renderer-Dk45W8Zd.mjs.map} +1 -1
  193. package/dist/{renderer-Dv5yE4LE.mjs → renderer-DlZwbO-A.mjs} +2 -2
  194. package/dist/{renderer-Dv5yE4LE.mjs.map → renderer-DlZwbO-A.mjs.map} +1 -1
  195. package/dist/{renderer-CaRPsrbX.mjs → renderer-DnPzvIHK.mjs} +2 -2
  196. package/dist/{renderer-CCnrG0Pd.mjs → renderer-DoRazdp_.mjs} +2 -2
  197. package/dist/{renderer-CCnrG0Pd.mjs.map → renderer-DoRazdp_.mjs.map} +1 -1
  198. package/dist/{renderer-CeIXVu9F.mjs → renderer-DsAoxQ8O.mjs} +2 -2
  199. package/dist/{renderer-CeIXVu9F.mjs.map → renderer-DsAoxQ8O.mjs.map} +1 -1
  200. package/dist/renderer-Dsp6xDw5.mjs +5 -0
  201. package/dist/renderer-DuC-BFXR.mjs +5 -0
  202. package/dist/renderer-DuWs_Wsv.mjs +5 -0
  203. package/dist/{renderer-8pS82_mX.mjs → renderer-DveQQCi8.mjs} +3 -3
  204. package/dist/{renderer-8pS82_mX.mjs.map → renderer-DveQQCi8.mjs.map} +1 -1
  205. package/dist/{renderer-Dypn26aP.mjs → renderer-DxHICEk3.mjs} +3 -3
  206. package/dist/{renderer-Dypn26aP.mjs.map → renderer-DxHICEk3.mjs.map} +1 -1
  207. package/dist/{renderer-DfTnsfy9.mjs → renderer-DxbFjINv.mjs} +3 -3
  208. package/dist/{renderer-DfTnsfy9.mjs.map → renderer-DxbFjINv.mjs.map} +1 -1
  209. package/dist/{renderer-jvgqy4Td.mjs → renderer-Gw1ICOJ3.mjs} +3 -3
  210. package/dist/{renderer-jvgqy4Td.mjs.map → renderer-Gw1ICOJ3.mjs.map} +1 -1
  211. package/dist/{renderer-j-3XB8YI.mjs → renderer-LY2MiIj0.mjs} +3 -3
  212. package/dist/{renderer-j-3XB8YI.mjs.map → renderer-LY2MiIj0.mjs.map} +1 -1
  213. package/dist/{renderer-BLrhcSex.mjs → renderer-LwrcnEtI.mjs} +3 -3
  214. package/dist/{renderer-BLrhcSex.mjs.map → renderer-LwrcnEtI.mjs.map} +1 -1
  215. package/dist/renderer-OSgsiIrW.mjs +5 -0
  216. package/dist/renderer-OZaMFYtZ.mjs +5 -0
  217. package/dist/renderer-T79TmHET.mjs +5 -0
  218. package/dist/{renderer-7Tx-L9Vf.mjs → renderer-U-12BOhe.mjs} +2 -2
  219. package/dist/{renderer-7Tx-L9Vf.mjs.map → renderer-U-12BOhe.mjs.map} +1 -1
  220. package/dist/renderer-UhsC9oTU.mjs +5 -0
  221. package/dist/renderer-WYZV1jWf.mjs +5 -0
  222. package/dist/renderer-Y7evDjHH.mjs +5 -0
  223. package/dist/{renderer-CQkZUsvc.mjs → renderer-cg06hb82.mjs} +3 -3
  224. package/dist/{renderer-CQkZUsvc.mjs.map → renderer-cg06hb82.mjs.map} +1 -1
  225. package/dist/{renderer-DRLtfxdW.mjs → renderer-eAVNsuX8.mjs} +95 -16
  226. package/dist/renderer-eAVNsuX8.mjs.map +1 -0
  227. package/dist/renderer-mcePkmUe.mjs +5 -0
  228. package/dist/{renderer-CT_aJBk2.mjs → renderer-uorPCWdC.mjs} +3 -3
  229. package/dist/{renderer-CT_aJBk2.mjs.map → renderer-uorPCWdC.mjs.map} +1 -1
  230. package/dist/{renderer-BMnX_-PZ.mjs → renderer-xLQ_nkmy.mjs} +3 -3
  231. package/dist/{renderer-BMnX_-PZ.mjs.map → renderer-xLQ_nkmy.mjs.map} +1 -1
  232. package/dist/{renderers-CP8CFWDs-fYqxoIKK.mjs → renderers-CP8CFWDs-ywy4xljX.mjs} +2 -2
  233. package/dist/{renderers-CP8CFWDs-fYqxoIKK.mjs.map → renderers-CP8CFWDs-ywy4xljX.mjs.map} +1 -1
  234. package/dist/signals/index.d.mts +5 -5
  235. package/dist/signals/index.mjs +4 -4
  236. package/dist/{src-DggYlKaX.mjs → src-B0KFNjj4.mjs} +289 -59
  237. package/dist/src-B0KFNjj4.mjs.map +1 -0
  238. package/package.json +58 -58
  239. package/dist/dist-CnFcSau0.mjs.map +0 -1
  240. package/dist/index-Ced1Ry4S.d.mts.map +0 -1
  241. package/dist/index-Ds9_v60R.d.mts.map +0 -1
  242. package/dist/preview-engine/chunk-3OXRGTQ2.mjs +0 -2390
  243. package/dist/preview-engine/chunk-7EYVBRN3.mjs +0 -480
  244. package/dist/preview-engine/chunk-7WBDEZ2A.mjs +0 -1974
  245. package/dist/preview-engine/chunk-HPP52AQI.mjs +0 -4
  246. package/dist/preview-engine/renderers-CCOEGTZJ.mjs +0 -228
  247. package/dist/program-CqQDWfSV.mjs.map +0 -1
  248. package/dist/renderer-68XHoE2b.mjs +0 -5
  249. package/dist/renderer-AqUForA-.mjs +0 -5
  250. package/dist/renderer-B0J-uGBG.mjs +0 -5
  251. package/dist/renderer-BF2R0rwb.mjs +0 -5
  252. package/dist/renderer-BLrO9lqK.mjs +0 -5
  253. package/dist/renderer-BMy8jXhL.mjs +0 -5
  254. package/dist/renderer-BUxENp9L.mjs +0 -5
  255. package/dist/renderer-C-ebryrM.mjs +0 -5
  256. package/dist/renderer-C8Gbs3kr.mjs +0 -5
  257. package/dist/renderer-CFZvMv2-.mjs +0 -5
  258. package/dist/renderer-CGXW7mOs.mjs +0 -5
  259. package/dist/renderer-CNq6OGbb.mjs +0 -5
  260. package/dist/renderer-CO-Ddz6-.mjs +0 -5
  261. package/dist/renderer-CRdxAsP7.mjs +0 -5
  262. package/dist/renderer-CZLo0ALi.mjs +0 -5
  263. package/dist/renderer-CdvVUkhR.mjs +0 -5
  264. package/dist/renderer-Cjg7kmTq.mjs +0 -5
  265. package/dist/renderer-CxKymhVa.mjs +0 -5
  266. package/dist/renderer-Cxlpu3pN.mjs +0 -5
  267. package/dist/renderer-Cz6qZ2Ii.mjs +0 -5
  268. package/dist/renderer-DDGvF2YA.mjs +0 -5
  269. package/dist/renderer-DFxtOKpi.mjs +0 -5
  270. package/dist/renderer-DGBNjTsB.mjs +0 -6
  271. package/dist/renderer-DRLtfxdW.mjs.map +0 -1
  272. package/dist/renderer-DUFcdZmw.mjs +0 -5
  273. package/dist/renderer-DUMu6EOH.mjs +0 -5
  274. package/dist/renderer-DdULrvJo.mjs +0 -5
  275. package/dist/renderer-DoaOOP6_.mjs +0 -5
  276. package/dist/renderer-DqmZqV-a.mjs +0 -5
  277. package/dist/renderer-LxTQ2E-G.mjs +0 -5
  278. package/dist/renderer-SpU-RddZ.mjs +0 -5
  279. package/dist/renderer-W6ahCM9z.mjs +0 -5
  280. package/dist/renderer-gGqtzw-S.mjs +0 -5
  281. package/dist/renderer-l0Wn1AoV.mjs +0 -5
  282. package/dist/renderer-rpXHvMZG.mjs +0 -5
  283. package/dist/renderer-twdsKOiz.mjs +0 -5
  284. package/dist/renderer-uJz-eIqO.mjs +0 -5
  285. package/dist/src-DggYlKaX.mjs.map +0 -1
@@ -10052,6 +10052,7 @@ function resolveEnvironment(opts) {
10052
10052
  * normal matrix computation for non-uniform surface scaling,
10053
10053
  * and binary uniform buffer packing for WGSL shaders.
10054
10054
  */
10055
+ /** Slots retained in the legacy uniform prefix; actual direct lights use storage. */
10055
10056
  const MAX_LIGHTS = 8;
10056
10057
  /**
10057
10058
  * 16 (ambient) + 16 (params) + 8 * 64 (lights), then the physical-shading
@@ -10255,7 +10256,7 @@ const Light3D = {
10255
10256
  ambientR += col[0] * int$1;
10256
10257
  ambientG += col[1] * int$1;
10257
10258
  ambientB += col[2] * int$1;
10258
- } else if (directLights.length < MAX_LIGHTS) directLights.push(Light3D.processLight(l));
10259
+ } else directLights.push(Light3D.processLight(l));
10259
10260
  buffer[0] = ambientR;
10260
10261
  buffer[1] = ambientG;
10261
10262
  buffer[2] = ambientB;
@@ -10287,12 +10288,42 @@ const Light3D = {
10287
10288
  }
10288
10289
  return buffer;
10289
10290
  },
10291
+ packDirectLights(lights, shadows = []) {
10292
+ const direct = lights.filter((light) => (light.lightType ?? light.type) !== "ambient");
10293
+ const data = new Float32Array(Math.max(1, direct.length) * 40);
10294
+ const shadowLayers = new Map(shadows.map((shadow, layer) => [shadow.lightIndex, layer]));
10295
+ for (const [i, light] of direct.entries()) {
10296
+ const packed = Light3D.processLight(light);
10297
+ const offset = i * 40;
10298
+ data.set(packed.posType, offset);
10299
+ data.set(packed.dirRadius, offset + 4);
10300
+ data.set(packed.colorIntensity, offset + 8);
10301
+ data.set(packed.spotParams, offset + 12);
10302
+ const layer = shadowLayers.get(i);
10303
+ const shadow = layer === void 0 ? void 0 : shadows[layer];
10304
+ if (shadow && layer !== void 0) {
10305
+ for (let j = 0; j < 16; j++) data[offset + 16 + j] = shadow.matrix[j] ?? 0;
10306
+ data.set([
10307
+ shadow.softness,
10308
+ shadow.mapSize,
10309
+ layer,
10310
+ 1
10311
+ ], offset + 32);
10312
+ data.set([
10313
+ shadow.bias,
10314
+ shadow.normalOffset,
10315
+ shadow.darkness,
10316
+ 0
10317
+ ], offset + 36);
10318
+ }
10319
+ }
10320
+ return data;
10321
+ },
10290
10322
  shadowCasterIndex(lights) {
10291
10323
  let index = 0;
10292
10324
  for (const l of lights) {
10293
10325
  const kind = l.lightType ?? l.type;
10294
10326
  if (kind === "ambient") continue;
10295
- if (index >= MAX_LIGHTS) break;
10296
10327
  if (l.castShadow && (kind === "directional" || kind === "spot")) return index;
10297
10328
  index++;
10298
10329
  }
@@ -10400,7 +10431,7 @@ var Path3D = class Path3D$1 {
10400
10431
  0,
10401
10432
  0
10402
10433
  ];
10403
- let b0 = Vector3Math.normalize(Vector3Math.cross(t0, n0));
10434
+ const b0 = Vector3Math.normalize(Vector3Math.cross(t0, n0));
10404
10435
  this.frames[0] = {
10405
10436
  normal: n0,
10406
10437
  binormal: b0,
@@ -10443,6 +10474,21 @@ var Path3D = class Path3D$1 {
10443
10474
  ];
10444
10475
  }
10445
10476
  /**
10477
+ * Returns a copy of the discrete samples `[position, tangent, distance]` along this path.
10478
+ */
10479
+ getSamples() {
10480
+ return this.samples;
10481
+ }
10482
+ /**
10483
+ * Evaluates `count` evenly arc-length-spaced 3D positions along the path.
10484
+ */
10485
+ getPoints(count = 64) {
10486
+ const n = Math.max(2, Math.round(count));
10487
+ const pts = [];
10488
+ for (let i = 0; i < n; i++) pts.push(this.getPointAt(i / (n - 1)).position);
10489
+ return pts;
10490
+ }
10491
+ /**
10446
10492
  * Evaluates point, tangent, normal, binormal, and rotation at normalized progress u in [0, 1].
10447
10493
  * Progression moves with constant velocity along the arc length of the path.
10448
10494
  */
@@ -17784,8 +17830,6 @@ function transformModelBounds(bounds, { offset, scale }) {
17784
17830
  * hierarchy into model-space matrices, and turns a skin's joints into the
17785
17831
  * joint matrices the mesh shader blends (joint world × inverse bind).
17786
17832
  */
17787
- /** Joints per skin the mesh shader holds (mesh3d.wgsl jointMatrices). */
17788
- const MAX_JOINTS = 128;
17789
17833
  /** Column-major translate · rotate(quaternion) · scale. */
17790
17834
  function trsMatrix(t, q, s) {
17791
17835
  const [x, y, z, w] = q;
@@ -17927,12 +17971,12 @@ function poseNodes(model, clip, time$2) {
17927
17971
  };
17928
17972
  return nodes.map((_, i) => resolve$1$1(i));
17929
17973
  }
17930
- /** The joint matrices of one skin for a posed scene graph, padded with identity to MAX_JOINTS. */
17974
+ /** The joint matrices of one skin for a posed scene graph. */
17931
17975
  function jointMatrices(model, skinIndex, world) {
17932
17976
  const skin = model.skins?.[skinIndex];
17933
17977
  if (!skin) throw new Error(`model has no skin ${skinIndex}`);
17934
- const out = new Float32Array(MAX_JOINTS * 16);
17935
- for (let j = 0; j < MAX_JOINTS; j++) {
17978
+ const out = new Float32Array(Math.max(1, skin.joints.length) * 16);
17979
+ for (let j = 0; j < Math.max(1, skin.joints.length); j++) {
17936
17980
  const node = skin.joints[j];
17937
17981
  if (node === void 0) {
17938
17982
  out.set(Matrix4Math.identity(), j * 16);
@@ -18354,7 +18398,6 @@ var FbxLoader = class FbxLoader$1 {
18354
18398
  influences.set(cp, list);
18355
18399
  }
18356
18400
  }
18357
- if (joints.length > MAX_JOINTS) throw new Error(`FBX skin has ${joints.length} bones; the mesh shader holds ${MAX_JOINTS}`);
18358
18401
  if (joints.length > 0) {
18359
18402
  skinIndex = skins.length;
18360
18403
  skins.push({
@@ -20054,7 +20097,6 @@ var GltfLoader = class GltfLoader$1 {
20054
20097
  });
20055
20098
  });
20056
20099
  }
20057
- for (const skin of skins) if (skin.joints.length > MAX_JOINTS) throw new Error(`glTF skin has ${skin.joints.length} joints; the mesh shader holds ${MAX_JOINTS}`);
20058
20100
  if (!Number.isFinite(minX)) {
20059
20101
  minX = minY = minZ = 0;
20060
20102
  maxX = maxY = maxZ = 0;
@@ -30950,6 +30992,16 @@ struct GPULight {
30950
30992
  spotParams : vec4<f32>, // x = cos(outer), y = cos(inner), z = decay, w = unused
30951
30993
  };
30952
30994
 
30995
+ struct DirectLight {
30996
+ posType : vec4<f32>,
30997
+ dirRadius : vec4<f32>,
30998
+ colorIntensity : vec4<f32>,
30999
+ spotParams : vec4<f32>,
31000
+ shadowMatrix : mat4x4<f32>,
31001
+ shadowParams : vec4<f32>,
31002
+ shadowBias : vec4<f32>,
31003
+ };
31004
+
30953
31005
  struct LightsUniforms {
30954
31006
  ambientColor : vec4<f32>, // rgb = ambient color * intensity (sRGB), w = unused
30955
31007
  params : vec4<f32>, // x = numLights, y = hasLights, z = physical (1), w = tone mapping (0 none, 1 ACES, 2 AgX, 3 PBR Neutral)
@@ -31132,25 +31184,26 @@ var<private> POISSON : array<vec2<f32>, 12> = array<vec2<f32>, 12>(
31132
31184
 
31133
31185
  /// 1 = fully lit, 0 = fully shadowed, for light index i.
31134
31186
  fn shadowFactor(worldPos: vec3<f32>, N: vec3<f32>, i: i32) -> f32 {
31135
- if (lights.shadowParams.x < 0.5 || i32(lights.shadowParams.y) != i) {
31187
+ let shadow = directLights[i];
31188
+ if (shadow.shadowParams.w < 0.5) {
31136
31189
  return 1.0;
31137
31190
  }
31138
- let p = worldPos + N * lights.shadowBias.y;
31139
- let lp = lights.shadowMatrix * vec4<f32>(p, 1.0);
31191
+ let p = worldPos + N * shadow.shadowBias.y;
31192
+ let lp = shadow.shadowMatrix * vec4<f32>(p, 1.0);
31140
31193
  let ndc = lp.xyz / lp.w;
31141
31194
  let uv = vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
31142
31195
  if (lp.w <= 0.0 || uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || ndc.z > 1.0) {
31143
31196
  return 1.0;
31144
31197
  }
31145
- let texel = 1.0 / max(lights.shadowParams.w, 1.0);
31146
- let radius = lights.shadowParams.z * texel;
31147
- let depthRef = ndc.z - lights.shadowBias.x;
31198
+ let texel = 1.0 / max(shadow.shadowParams.y, 1.0);
31199
+ let radius = shadow.shadowParams.x * texel;
31200
+ let depthRef = ndc.z - shadow.shadowBias.x;
31148
31201
  var lit = 0.0;
31149
31202
  for (var k = 0; k < 12; k++) {
31150
- lit += textureSampleCompareLevel(shadowMap, shadowSampler, uv + POISSON[k] * radius, depthRef);
31203
+ lit += textureSampleCompareLevel(shadowMap, shadowSampler, uv + POISSON[k] * radius, i32(shadow.shadowParams.z), depthRef);
31151
31204
  }
31152
31205
  lit = lit / 12.0;
31153
- return mix(1.0, lit, lights.shadowBias.z);
31206
+ return mix(1.0, lit, shadow.shadowBias.z);
31154
31207
  }
31155
31208
 
31156
31209
  // ── Lights ────────────────────────────────────────────────────────────
@@ -31163,7 +31216,7 @@ struct LightSample {
31163
31216
 
31164
31217
  fn sampleLight(i: i32, worldPos: vec3<f32>) -> LightSample {
31165
31218
  var out = LightSample(vec3<f32>(0.0, 0.0, 1.0), 1.0, false);
31166
- let light = lights.lights[i];
31219
+ let light = directLights[i];
31167
31220
  let lightType = light.posType.w;
31168
31221
  if (lightType > 0.5 && lightType < 1.5) {
31169
31222
  out.L = normalize(-light.dirRadius.xyz);
@@ -31212,15 +31265,12 @@ fn applyLighting3D(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3<
31212
31265
 
31213
31266
  var totalIllumination = baseColor * (lights.ambientColor.rgb * ambientInt);
31214
31267
  let numLights = i32(lights.params.x);
31215
- for (var i = 0; i < 8; i++) {
31216
- if (i >= numLights) {
31217
- break;
31218
- }
31268
+ for (var i = 0; i < numLights; i++) {
31219
31269
  let ls = sampleLight(i, worldPos);
31220
31270
  if (!ls.valid) {
31221
31271
  continue;
31222
31272
  }
31223
- let light = lights.lights[i];
31273
+ let light = directLights[i];
31224
31274
  let lightColor = light.colorIntensity.rgb;
31225
31275
  let intensity = light.colorIntensity.w;
31226
31276
  let NdotL = max(dot(N, ls.L), 0.0);
@@ -31253,15 +31303,12 @@ fn shadePhysical(worldPos: vec3<f32>, worldNormal: vec3<f32>, albedo: vec3<f32>,
31253
31303
 
31254
31304
  var lo = vec3<f32>(0.0);
31255
31305
  let numLights = i32(lights.params.x);
31256
- for (var i = 0; i < 8; i++) {
31257
- if (i >= numLights) {
31258
- break;
31259
- }
31306
+ for (var i = 0; i < numLights; i++) {
31260
31307
  let ls = sampleLight(i, worldPos);
31261
31308
  if (!ls.valid) {
31262
31309
  continue;
31263
31310
  }
31264
- let light = lights.lights[i];
31311
+ let light = directLights[i];
31265
31312
  let nDotL = max(dot(N, ls.L), 0.0);
31266
31313
  if (nDotL <= 0.0) {
31267
31314
  continue;
@@ -31358,8 +31405,9 @@ fn shadeSurface(worldPos: vec3<f32>, worldNormal: vec3<f32>, baseColor: vec3<f32
31358
31405
  /** Group-3 bindings every lit 3D shader declares. */
31359
31406
  const lightsBindingsWgsl = `
31360
31407
  @group(3) @binding(0) var<uniform> lights : LightsUniforms;
31361
- @group(3) @binding(1) var shadowMap : texture_depth_2d;
31408
+ @group(3) @binding(1) var shadowMap : texture_depth_2d_array;
31362
31409
  @group(3) @binding(2) var shadowSampler : sampler_comparison;
31410
+ @group(3) @binding(3) var<storage, read> directLights : array<DirectLight>;
31363
31411
  `;
31364
31412
  /**
31365
31413
  * The 3D pipelines draw into one of four target sets: color; color + linear
@@ -32385,7 +32433,7 @@ fn matteCoverage(fragPos : vec2<f32>) -> f32 {
32385
32433
  }
32386
32434
  /**
32387
32435
  * WebGPU WGSL shaders for 3D static and skinned meshes.
32388
- * Supports GPU Linear Blend Skinning (up to 128 bones),
32436
+ * Supports GPU Linear Blend Skinning,
32389
32437
  * the shared 3D surface shading (Blinn-Phong, or physical GGX + image-based light
32390
32438
  * in scenes with an environment) or toon shading, shadow maps, emissive,
32391
32439
  * alpha-cutoff (glTF MASK) materials,
@@ -32398,7 +32446,7 @@ struct ModelUniforms {
32398
32446
  modelMatrix : mat4x4<f32>,
32399
32447
  normalMatrix : mat4x4<f32>,
32400
32448
  colorTint : vec4<f32>,
32401
- params : vec4<f32>, // x = opacity, y = twoSided, z = isSkinned, w = flags (bit 0 instanced, bit 1 map UVs)
32449
+ params : vec4<f32>, // x = opacity, y = twoSided, z = isSkinned, w = flags (bit 0 instanced, bit 1 map UVs, bit 2 matrix instances)
32402
32450
  materialParams : vec4<f32>, // x = shininess, y = specularIntensity, z = ambientIntensity, w = materialMode (0=unlit, 1=lit, 2=toon)
32403
32451
  shade : vec4<f32>, // rgb = toon shadow-side color multiplier, a = alpha cutoff (0 = none)
32404
32452
  pbr : vec4<f32>, // x = roughness (<0 = default), y = metallic
@@ -32407,7 +32455,7 @@ struct ModelUniforms {
32407
32455
  };
32408
32456
 
32409
32457
  struct SkinningUniforms {
32410
- jointMatrices : array<mat4x4<f32>, 128>,
32458
+ jointMatrices : array<mat4x4<f32>>,
32411
32459
  };
32412
32460
 
32413
32461
  struct InstanceData {
@@ -32419,7 +32467,7 @@ struct InstanceData {
32419
32467
 
32420
32468
  @group(0) @binding(0) var<uniform> cam : CameraUniforms;
32421
32469
  @group(1) @binding(0) var<uniform> model : ModelUniforms;
32422
- @group(1) @binding(1) var<uniform> skinning : SkinningUniforms;
32470
+ @group(1) @binding(1) var<storage, read> skinning : SkinningUniforms;
32423
32471
  ${matte3dWgsl(1, 2)}@group(1) @binding(3) var backdropTex : texture_2d<f32>;
32424
32472
  @group(1) @binding(4) var<storage, read> instances : array<InstanceData>;
32425
32473
  @group(2) @binding(0) var texSampler : sampler;
@@ -32505,7 +32553,14 @@ fn vs_main(
32505
32553
  var instColor = vec4<f32>(1.0);
32506
32554
  var instEmissive = vec4<f32>(0.0);
32507
32555
 
32508
- if ((u32(model.params.w) & 1u) != 0u) {
32556
+ if ((u32(model.params.w) & 4u) != 0u) {
32557
+ let a = instances[instanceIndex * 2u];
32558
+ let b = instances[instanceIndex * 2u + 1u];
32559
+ let instanceModel = mat4x4<f32>(a.posScale, a.quat, a.color, a.emissive);
32560
+ let instanceNormal = mat4x4<f32>(b.posScale, b.quat, b.color, b.emissive);
32561
+ localPos = (instanceModel * vec4<f32>(localPos, 1.0)).xyz;
32562
+ localNorm = (instanceNormal * vec4<f32>(localNorm, 0.0)).xyz;
32563
+ } else if ((u32(model.params.w) & 1u) != 0u) {
32509
32564
  let inst = instances[instanceIndex];
32510
32565
  let scaledPos = localPos * inst.posScale.w;
32511
32566
  localPos = rotateVecByQuat(scaledPos, inst.quat) + inst.posScale.xyz;
@@ -33057,12 +33112,20 @@ function createLightsLayout(device, label = "Lights3DLayout") {
33057
33112
  {
33058
33113
  binding: 1,
33059
33114
  visibility: GPUShaderStage.FRAGMENT,
33060
- texture: { sampleType: "depth" }
33115
+ texture: {
33116
+ sampleType: "depth",
33117
+ viewDimension: "2d-array"
33118
+ }
33061
33119
  },
33062
33120
  {
33063
33121
  binding: 2,
33064
33122
  visibility: GPUShaderStage.FRAGMENT,
33065
33123
  sampler: { type: "comparison" }
33124
+ },
33125
+ {
33126
+ binding: 3,
33127
+ visibility: GPUShaderStage.FRAGMENT,
33128
+ buffer: { type: "read-only-storage" }
33066
33129
  }
33067
33130
  ]
33068
33131
  });
@@ -33367,7 +33430,7 @@ var Mesh3DPipeline = class {
33367
33430
  constructor(device, format, depthFormat = "depth24plus", cameraLayout, lightsLayout, sampleCount = 1) {
33368
33431
  this.device = device;
33369
33432
  this.modelPool = new BufferPool((52 + SURFACE_PARAMS_FLOATS) * 4, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
33370
- this.skinningPool = new BufferPool(8192, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
33433
+ this.skinningPool = new BufferPool(64, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
33371
33434
  const module$1 = device.createShaderModule({
33372
33435
  label: "mesh3d.wgsl",
33373
33436
  code: mesh3dWgsl
@@ -33391,7 +33454,7 @@ var Mesh3DPipeline = class {
33391
33454
  {
33392
33455
  binding: 1,
33393
33456
  visibility: GPUShaderStage.VERTEX,
33394
- buffer: { type: "uniform" }
33457
+ buffer: { type: "read-only-storage" }
33395
33458
  },
33396
33459
  matteLayoutEntry(2),
33397
33460
  matteLayoutEntry(3),
@@ -33642,16 +33705,11 @@ var Mesh3DPipeline = class {
33642
33705
  width: 1,
33643
33706
  height: 1
33644
33707
  });
33645
- const idMatrices = new Float32Array(2048);
33646
- for (let b = 0; b < 128; b++) {
33647
- idMatrices[b * 16] = 1;
33648
- idMatrices[b * 16 + 5] = 1;
33649
- idMatrices[b * 16 + 10] = 1;
33650
- idMatrices[b * 16 + 15] = 1;
33651
- }
33708
+ const idMatrices = new Float32Array(16);
33709
+ idMatrices[0] = idMatrices[5] = idMatrices[10] = idMatrices[15] = 1;
33652
33710
  this.defaultSkinningBuffer = device.createBuffer({
33653
- size: 8192,
33654
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
33711
+ size: 64,
33712
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
33655
33713
  mappedAtCreation: true
33656
33714
  });
33657
33715
  new Float32Array(this.defaultSkinningBuffer.getMappedRange()).set(idMatrices);
@@ -33690,7 +33748,7 @@ var Mesh3DPipeline = class {
33690
33748
  this.modelData[36] = data.opacity ?? 1;
33691
33749
  this.modelData[37] = data.twoSided ? 1 : 0;
33692
33750
  this.modelData[38] = data.isSkinned ? 1 : 0;
33693
- this.modelData[39] = (data.isInstanced || data.instanceBuffer ? 1 : 0) + (data.hasMapUvs ? 2 : 0);
33751
+ this.modelData[39] = (data.isInstanced || data.instanceBuffer ? 1 : 0) + (data.hasMapUvs ? 2 : 0) + (data.matrixInstances ? 4 : 0);
33694
33752
  const shininess = data.roughness !== void 0 ? Math.max(1, (1 - data.roughness) * 128) : data.shininess ?? 32;
33695
33753
  const specInt = data.metallic !== void 0 ? data.metallic : data.specularIntensity ?? .5;
33696
33754
  const ambientInt = data.ambientIntensity ?? 1;
@@ -33723,6 +33781,7 @@ var Mesh3DPipeline = class {
33723
33781
  this.modelData[51] = glass.ior;
33724
33782
  }
33725
33783
  const modelBuffer = this.modelPool.getBuffer(this.device, this.modelData);
33784
+ if (data.skinningMatrices && data.skinningMatrices.byteLength > this.device.limits.maxStorageBufferBindingSize) throw new Error(`Skinning data requires ${data.skinningMatrices.byteLength} bytes, exceeding the device storage binding limit of ${this.device.limits.maxStorageBufferBindingSize} bytes`);
33726
33785
  const skinningBuffer = data.isSkinned && data.skinningMatrices ? this.skinningPool.getBuffer(this.device, data.skinningMatrices) : this.defaultSkinningBuffer;
33727
33786
  return this.device.createBindGroup({
33728
33787
  layout: this.modelLayout,
@@ -34119,37 +34178,13 @@ var MotionBlurPipeline = class {
34119
34178
  }
34120
34179
  };
34121
34180
  function parseHexOrRgbColor(colorStr) {
34122
- const c = colorStr.trim();
34123
- if (c.startsWith("#")) {
34124
- const hex = c.slice(1);
34125
- if (hex.length === 3) return [
34126
- Number.parseInt(hex[0] + hex[0], 16) / 255,
34127
- Number.parseInt(hex[1] + hex[1], 16) / 255,
34128
- Number.parseInt(hex[2] + hex[2], 16) / 255,
34129
- 1
34130
- ];
34131
- if (hex.length === 6) return [
34132
- Number.parseInt(hex.slice(0, 2), 16) / 255,
34133
- Number.parseInt(hex.slice(2, 4), 16) / 255,
34134
- Number.parseInt(hex.slice(4, 6), 16) / 255,
34135
- 1
34136
- ];
34137
- if (hex.length === 8) return [
34138
- Number.parseInt(hex.slice(0, 2), 16) / 255,
34139
- Number.parseInt(hex.slice(2, 4), 16) / 255,
34140
- Number.parseInt(hex.slice(4, 6), 16) / 255,
34141
- Number.parseInt(hex.slice(6, 8), 16) / 255
34142
- ];
34143
- }
34144
- if (c.startsWith("rgb")) {
34145
- const parts = c.replace(/rgba?\(/, "").replace(/\)/, "").split(",").map((p) => Number.parseFloat(p.trim()));
34146
- return [
34147
- (parts[0] ?? 0) / 255,
34148
- (parts[1] ?? 0) / 255,
34149
- (parts[2] ?? 0) / 255,
34150
- parts[3] !== void 0 ? parts[3] : 1
34151
- ];
34152
- }
34181
+ const c = parseColor(colorStr);
34182
+ if (c.a > 0 || colorStr.trim().toLowerCase() === "transparent") return [
34183
+ c.r,
34184
+ c.g,
34185
+ c.b,
34186
+ c.a
34187
+ ];
34153
34188
  return [
34154
34189
  1,
34155
34190
  1,
@@ -34298,7 +34333,7 @@ function extractGlyphContours(glyph, glyphX, glyphY, scale, flattening) {
34298
34333
  };
34299
34334
  }
34300
34335
  /** Side-wall segments meeting at more than this angle (30°) keep a hard edge. */
34301
- const SIDE_CREASE_COS = Math.cos(30 * Math.PI / 180);
34336
+ const SIDE_CREASE_COS$1 = Math.cos(30 * Math.PI / 180);
34302
34337
  /**
34303
34338
  * Group a glyph's contours into filled outlines and their holes by nesting
34304
34339
  * depth: a contour inside an even number of others is an outline (the ring
@@ -34597,7 +34632,7 @@ function generateExtrudedTextGeometry(options) {
34597
34632
  const u = along / perimeter;
34598
34633
  const sx = incoming.x + outgoing.x;
34599
34634
  const sy = incoming.y + outgoing.y;
34600
- const averaged = incoming.x * outgoing.x + incoming.y * outgoing.y >= SIDE_CREASE_COS ? {
34635
+ const averaged = incoming.x * outgoing.x + incoming.y * outgoing.y >= SIDE_CREASE_COS$1 ? {
34601
34636
  x: sx / Math.hypot(sx, sy),
34602
34637
  y: sy / Math.hypot(sx, sy)
34603
34638
  } : null;
@@ -34736,11 +34771,14 @@ var MeshBuilder = class {
34736
34771
  positions = [];
34737
34772
  normals = [];
34738
34773
  uvs = [];
34774
+ colors = [];
34739
34775
  indices = [];
34740
- vertex(p, n, uv) {
34776
+ topology;
34777
+ vertex(p, n, uv, color) {
34741
34778
  this.positions.push(p[0], p[1], p[2]);
34742
34779
  this.normals.push(n[0], n[1], n[2]);
34743
34780
  this.uvs.push(uv[0], uv[1]);
34781
+ if (color) this.colors.push(color[0], color[1], color[2], color[3]);
34744
34782
  return this.positions.length / 3 - 1;
34745
34783
  }
34746
34784
  /**
@@ -34760,7 +34798,7 @@ var MeshBuilder = class {
34760
34798
  }
34761
34799
  build(name) {
34762
34800
  const count = this.positions.length / 3;
34763
- return {
34801
+ const mesh = {
34764
34802
  id: `${name}-mesh`,
34765
34803
  name,
34766
34804
  positions: new Float32Array(this.positions),
@@ -34769,6 +34807,9 @@ var MeshBuilder = class {
34769
34807
  indices: count > 65535 ? new Uint32Array(this.indices) : new Uint16Array(this.indices),
34770
34808
  materialName: `${name}-material`
34771
34809
  };
34810
+ if (this.topology) mesh.topology = this.topology;
34811
+ if (this.colors.length === count * 4 && this.colors.length > 0) mesh.colors = new Float32Array(this.colors);
34812
+ return mesh;
34772
34813
  }
34773
34814
  };
34774
34815
  function normalize(v) {
@@ -34783,6 +34824,23 @@ function normalize(v) {
34783
34824
  0
34784
34825
  ];
34785
34826
  }
34827
+ function sub3(a, b) {
34828
+ return [
34829
+ a[0] - b[0],
34830
+ a[1] - b[1],
34831
+ a[2] - b[2]
34832
+ ];
34833
+ }
34834
+ function dot3(a, b) {
34835
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
34836
+ }
34837
+ function cross3(a, b) {
34838
+ return [
34839
+ a[1] * b[2] - a[2] * b[1],
34840
+ a[2] * b[0] - a[0] * b[2],
34841
+ a[0] * b[1] - a[1] * b[0]
34842
+ ];
34843
+ }
34786
34844
  /**
34787
34845
  * 1D samples across a side of half-extent `e` whose ends round off with
34788
34846
  * radius `r`: arc-spaced through each rounded zone, nothing in between, so
@@ -34943,29 +35001,87 @@ function sphere(o, mb) {
34943
35001
  }
34944
35002
  function cylinder(o, mb) {
34945
35003
  const seg = Math.max(3, Math.round(o.segments ?? 32));
34946
- const rTop = o.radiusTop ?? o.radius ?? 50;
34947
- const rBottom = o.radiusBottom ?? o.radius ?? 50;
35004
+ const rTop = Math.max(0, o.radiusTop ?? o.radius ?? 50);
35005
+ const rBottom = Math.max(0, o.radiusBottom ?? o.radius ?? 50);
34948
35006
  const hy = o.height / 2;
34949
- const lean = o.height > 0 ? (rBottom - rTop) / o.height : 0;
34950
- for (let j = 0; j <= 1; j++) {
34951
- const rr = j === 0 ? rTop : rBottom;
34952
- const y = j === 0 ? -hy : hy;
34953
- for (let i = 0; i <= seg; i++) {
34954
- const theta = i / seg * Math.PI * 2;
34955
- const c = Math.cos(theta);
34956
- const s = Math.sin(theta);
34957
- mb.vertex([
34958
- c * rr,
34959
- y,
34960
- s * rr
34961
- ], normalize([
34962
- c,
34963
- -lean,
34964
- s
34965
- ]), [i / seg, j]);
35007
+ if (o.flatShaded) for (let i = 0; i < seg; i++) {
35008
+ const theta0 = i / seg * Math.PI * 2;
35009
+ const theta1 = (i + 1) / seg * Math.PI * 2;
35010
+ const c0 = Math.cos(theta0);
35011
+ const s0 = Math.sin(theta0);
35012
+ const c1 = Math.cos(theta1);
35013
+ const s1 = Math.sin(theta1);
35014
+ const t0 = [
35015
+ c0 * rTop,
35016
+ -hy,
35017
+ s0 * rTop
35018
+ ];
35019
+ const t1 = [
35020
+ c1 * rTop,
35021
+ -hy,
35022
+ s1 * rTop
35023
+ ];
35024
+ const b0 = [
35025
+ c0 * rBottom,
35026
+ hy,
35027
+ s0 * rBottom
35028
+ ];
35029
+ const b1 = [
35030
+ c1 * rBottom,
35031
+ hy,
35032
+ s1 * rBottom
35033
+ ];
35034
+ const fn = normalize(cross3(sub3(b0, t0), rBottom >= rTop ? sub3(b1, b0) : sub3(t1, t0)));
35035
+ const u0 = i / seg;
35036
+ const u1 = (i + 1) / seg;
35037
+ if (rTop <= 1e-6) {
35038
+ const apex = mb.vertex([
35039
+ 0,
35040
+ -hy,
35041
+ 0
35042
+ ], fn, [(u0 + u1) / 2, 0]);
35043
+ const vB0 = mb.vertex(b0, fn, [u0, 1]);
35044
+ const vB1 = mb.vertex(b1, fn, [u1, 1]);
35045
+ mb.indices.push(apex, vB0, vB1);
35046
+ } else if (rBottom <= 1e-6) {
35047
+ const vT0 = mb.vertex(t0, fn, [u0, 0]);
35048
+ const vT1 = mb.vertex(t1, fn, [u1, 0]);
35049
+ const apex = mb.vertex([
35050
+ 0,
35051
+ hy,
35052
+ 0
35053
+ ], fn, [(u0 + u1) / 2, 1]);
35054
+ mb.indices.push(vT0, apex, vT1);
35055
+ } else {
35056
+ const base = mb.vertex(t0, fn, [u0, 0]);
35057
+ mb.vertex(t1, fn, [u1, 0]);
35058
+ mb.vertex(b0, fn, [u0, 1]);
35059
+ mb.vertex(b1, fn, [u1, 1]);
35060
+ mb.indices.push(base, base + 2, base + 1, base + 1, base + 2, base + 3);
35061
+ }
35062
+ }
35063
+ else {
35064
+ const lean = o.height > 0 ? (rBottom - rTop) / o.height : 0;
35065
+ for (let j = 0; j <= 1; j++) {
35066
+ const rr = j === 0 ? rTop : rBottom;
35067
+ const y = j === 0 ? -hy : hy;
35068
+ for (let i = 0; i <= seg; i++) {
35069
+ const theta = i / seg * Math.PI * 2;
35070
+ const c = Math.cos(theta);
35071
+ const s = Math.sin(theta);
35072
+ mb.vertex([
35073
+ c * rr,
35074
+ y,
35075
+ s * rr
35076
+ ], normalize([
35077
+ c,
35078
+ -lean,
35079
+ s
35080
+ ]), [i / seg, j]);
35081
+ }
34966
35082
  }
35083
+ mb.grid(0, seg, 1);
34967
35084
  }
34968
- mb.grid(0, seg, 1);
34969
35085
  if (o.openEnded) return;
34970
35086
  for (const [y, rr, ny] of [[
34971
35087
  -hy,
@@ -34976,7 +35092,7 @@ function cylinder(o, mb) {
34976
35092
  rBottom,
34977
35093
  1
34978
35094
  ]]) {
34979
- if (rr <= 0) continue;
35095
+ if (rr <= 1e-6) continue;
34980
35096
  const center = mb.vertex([
34981
35097
  0,
34982
35098
  y,
@@ -34986,7 +35102,7 @@ function cylinder(o, mb) {
34986
35102
  ny,
34987
35103
  0
34988
35104
  ], [.5, .5]);
34989
- const ring = mb.positions.length / 3;
35105
+ const ring$1 = mb.positions.length / 3;
34990
35106
  for (let i = 0; i <= seg; i++) {
34991
35107
  const theta = i / seg * Math.PI * 2;
34992
35108
  const c = Math.cos(theta);
@@ -35001,97 +35117,2014 @@ function cylinder(o, mb) {
35001
35117
  0
35002
35118
  ], [(c + 1) / 2, (s + 1) / 2]);
35003
35119
  }
35004
- for (let i = 0; i < seg; i++) if (ny < 0) mb.indices.push(center, ring + i, ring + i + 1);
35005
- else mb.indices.push(center, ring + i + 1, ring + i);
35120
+ for (let i = 0; i < seg; i++) if (ny < 0) mb.indices.push(center, ring$1 + i, ring$1 + i + 1);
35121
+ else mb.indices.push(center, ring$1 + i + 1, ring$1 + i);
35006
35122
  }
35007
35123
  }
35008
- function shard(o, mb) {
35009
- const r = o.radius ?? 50;
35010
- const hx = (o.size ? o.size[0] : r) || 50;
35011
- const hy = (o.size ? o.size[1] : r) || 50;
35012
- const hz = (o.size ? o.size[2] : r) || 50;
35013
- const maxR = Math.max(hx, hy, hz);
35014
- const facets = Math.max(4, Math.min(64, Math.round(o.facets ?? 14)));
35015
- const seed = Math.round(o.seed ?? 1) >>> 0;
35016
- const irregularity = Math.max(0, Math.min(.85, o.irregularity ?? .35));
35017
- let state = BigInt(seed) * 6364136223846793005n + 1442695040888963407n;
35018
- const rnd = () => {
35019
- state = state * 6364136223846793005n + 1442695040888963407n;
35020
- const xorshifted = Number((state >> 18n ^ state) >> 27n) >>> 0;
35021
- const rot = Number(state >> 59n) >>> 0;
35022
- return ((xorshifted >>> rot | xorshifted << (32 - rot & 31)) >>> 0) / 4294967296;
35023
- };
35024
- const sub3 = (a, b) => [
35025
- a[0] - b[0],
35026
- a[1] - b[1],
35027
- a[2] - b[2]
35028
- ];
35029
- const dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
35030
- const cross3 = (a, b) => [
35031
- a[1] * b[2] - a[2] * b[1],
35032
- a[2] * b[0] - a[0] * b[2],
35033
- a[0] * b[1] - a[1] * b[0]
35034
- ];
35035
- let faces = [
35036
- [
35037
- [
35038
- -hx,
35039
- -hy,
35124
+ /**
35125
+ * Torus centered at origin lying in the XY plane (so its hole faces a default
35126
+ * camera looking along +Z), with major radius `radius` and tube radius `tube`.
35127
+ */
35128
+ function torus(o, mb) {
35129
+ const R = Math.max(.001, o.radius ?? 60);
35130
+ const r = Math.max(.001, o.tube ?? 20);
35131
+ const radialSegments = Math.max(3, Math.round(o.radialSegments ?? 24));
35132
+ const tubularSegments = Math.max(3, Math.round(o.tubularSegments ?? 48));
35133
+ const arcRad = (o.arc ?? 360) * Math.PI / 180;
35134
+ const base = mb.positions.length / 3;
35135
+ for (let j = 0; j <= radialSegments; j++) {
35136
+ const v = j / radialSegments * Math.PI * 2;
35137
+ const cv = Math.cos(v);
35138
+ const sv = Math.sin(v);
35139
+ for (let i = 0; i <= tubularSegments; i++) {
35140
+ const u = i / tubularSegments * arcRad;
35141
+ const cu = Math.cos(u);
35142
+ const su = Math.sin(u);
35143
+ const x = (R + r * cv) * cu;
35144
+ const y = (R + r * cv) * su;
35145
+ const z = r * sv;
35146
+ const nx = cv * cu;
35147
+ const ny = cv * su;
35148
+ const nz = sv;
35149
+ mb.vertex([
35150
+ x,
35151
+ y,
35152
+ z
35153
+ ], normalize([
35154
+ nx,
35155
+ ny,
35156
+ nz
35157
+ ]), [i / tubularSegments, j / radialSegments]);
35158
+ }
35159
+ }
35160
+ mb.grid(base, tubularSegments, radialSegments, true);
35161
+ if (Math.abs(arcRad) < Math.PI * 2 - 1e-4) for (const [u, sign] of [[0, -1], [arcRad, 1]]) {
35162
+ const cu = Math.cos(u);
35163
+ const su = Math.sin(u);
35164
+ const capN = [
35165
+ -su * sign,
35166
+ cu * sign,
35167
+ 0
35168
+ ];
35169
+ const center = mb.vertex([
35170
+ R * cu,
35171
+ R * su,
35172
+ 0
35173
+ ], capN, [.5, .5]);
35174
+ const ring$1 = mb.positions.length / 3;
35175
+ for (let j = 0; j <= radialSegments; j++) {
35176
+ const v = j / radialSegments * Math.PI * 2;
35177
+ const cv = Math.cos(v);
35178
+ const sv = Math.sin(v);
35179
+ mb.vertex([
35180
+ (R + r * cv) * cu,
35181
+ (R + r * cv) * su,
35182
+ r * sv
35183
+ ], capN, [(cv + 1) / 2, (sv + 1) / 2]);
35184
+ }
35185
+ for (let j = 0; j < radialSegments; j++) if (sign < 0) mb.indices.push(center, ring$1 + j, ring$1 + j + 1);
35186
+ else mb.indices.push(center, ring$1 + j + 1, ring$1 + j);
35187
+ }
35188
+ }
35189
+ /**
35190
+ * Capsule aligned along the Y axis: a cylinder of height `length` and radius `radius`
35191
+ * capped by top (-y) and bottom (+y) hemispheres of radius `radius`.
35192
+ */
35193
+ function capsule(o, mb) {
35194
+ const r = Math.max(.001, o.radius ?? 30);
35195
+ const length = Math.max(0, o.length ?? 60);
35196
+ const hy = length / 2;
35197
+ const capSeg = Math.max(2, Math.round(o.capSegments ?? 12));
35198
+ const radSeg = Math.max(3, Math.round(o.radialSegments ?? 32));
35199
+ const rings = [];
35200
+ const totalArc = Math.PI * r + length;
35201
+ for (let j = 0; j <= capSeg; j++) {
35202
+ const phi = j / capSeg * Math.PI / 2;
35203
+ const s = Math.sin(phi);
35204
+ const c = Math.cos(phi);
35205
+ rings.push({
35206
+ y: -hy - r * c,
35207
+ rRing: r * s,
35208
+ ny: -c,
35209
+ nr: s,
35210
+ v: phi * r / (totalArc || 1)
35211
+ });
35212
+ }
35213
+ for (let j = 0; j <= capSeg; j++) {
35214
+ if (j === 0 && length <= 1e-6) continue;
35215
+ const phi = Math.PI / 2 + j / capSeg * Math.PI / 2;
35216
+ const s = Math.sin(phi);
35217
+ const c = Math.cos(phi);
35218
+ rings.push({
35219
+ y: hy - r * c,
35220
+ rRing: r * s,
35221
+ ny: -c,
35222
+ nr: s,
35223
+ v: (Math.PI / 2 * r + length + (phi - Math.PI / 2) * r) / (totalArc || 1)
35224
+ });
35225
+ }
35226
+ const base = mb.positions.length / 3;
35227
+ for (const ring$1 of rings) for (let i = 0; i <= radSeg; i++) {
35228
+ const theta = i / radSeg * Math.PI * 2;
35229
+ const ct = Math.cos(theta);
35230
+ const st = Math.sin(theta);
35231
+ const n = normalize([
35232
+ ring$1.nr * ct,
35233
+ ring$1.ny,
35234
+ ring$1.nr * st
35235
+ ]);
35236
+ mb.vertex([
35237
+ ring$1.rRing * ct,
35238
+ ring$1.y,
35239
+ ring$1.rRing * st
35240
+ ], n, [i / radSeg, ring$1.v]);
35241
+ }
35242
+ mb.grid(base, radSeg, rings.length - 1, false);
35243
+ }
35244
+ /**
35245
+ * Flat or thick-walled annulus (ring) in the XY plane facing -Z (front) and +Z (back).
35246
+ * Always builds a closed two-sided or solid manifold so it works with single-sided culling.
35247
+ */
35248
+ function ring(o, mb) {
35249
+ const rInner = Math.max(0, o.innerRadius ?? 30);
35250
+ const rOuter = Math.max(rInner + .001, o.outerRadius ?? 60);
35251
+ const hz = Math.max(0, o.depth ?? 0) / 2;
35252
+ const thetaSeg = Math.max(3, Math.round(o.thetaSegments ?? 48));
35253
+ const phiSeg = Math.max(1, Math.round(o.phiSegments ?? 1));
35254
+ const thetaStart = (o.thetaStart ?? 0) * Math.PI / 180;
35255
+ const thetaLength = (o.thetaLength ?? 360) * Math.PI / 180;
35256
+ for (const [z, nz, flip] of [[
35257
+ -hz,
35258
+ -1,
35259
+ false
35260
+ ], [
35261
+ hz,
35262
+ 1,
35263
+ true
35264
+ ]]) {
35265
+ const base = mb.positions.length / 3;
35266
+ for (let j = 0; j <= phiSeg; j++) {
35267
+ const radius = rInner + (rOuter - rInner) * j / phiSeg;
35268
+ for (let i = 0; i <= thetaSeg; i++) {
35269
+ const theta = thetaStart + i / thetaSeg * thetaLength;
35270
+ const c = Math.cos(theta);
35271
+ const s = Math.sin(theta);
35272
+ const x = radius * c;
35273
+ const y = radius * s;
35274
+ mb.vertex([
35275
+ x,
35276
+ y,
35277
+ z
35278
+ ], [
35279
+ 0,
35280
+ 0,
35281
+ nz
35282
+ ], [(x / rOuter + 1) / 2, (y / rOuter + 1) / 2]);
35283
+ }
35284
+ }
35285
+ mb.grid(base, thetaSeg, phiSeg, !flip);
35286
+ }
35287
+ if (hz > 0) {
35288
+ const outerBase = mb.positions.length / 3;
35289
+ for (const [z, v] of [[-hz, 0], [hz, 1]]) for (let i = 0; i <= thetaSeg; i++) {
35290
+ const theta = thetaStart + i / thetaSeg * thetaLength;
35291
+ const c = Math.cos(theta);
35292
+ const s = Math.sin(theta);
35293
+ mb.vertex([
35294
+ rOuter * c,
35295
+ rOuter * s,
35296
+ z
35297
+ ], [
35298
+ c,
35299
+ s,
35300
+ 0
35301
+ ], [i / thetaSeg, v]);
35302
+ }
35303
+ mb.grid(outerBase, thetaSeg, 1, true);
35304
+ if (rInner > 1e-6) {
35305
+ const innerBase = mb.positions.length / 3;
35306
+ for (const [z, v] of [[-hz, 0], [hz, 1]]) for (let i = 0; i <= thetaSeg; i++) {
35307
+ const theta = thetaStart + i / thetaSeg * thetaLength;
35308
+ const c = Math.cos(theta);
35309
+ const s = Math.sin(theta);
35310
+ mb.vertex([
35311
+ rInner * c,
35312
+ rInner * s,
35313
+ z
35314
+ ], [
35315
+ -c,
35316
+ -s,
35317
+ 0
35318
+ ], [i / thetaSeg, v]);
35319
+ }
35320
+ mb.grid(innerBase, thetaSeg, 1, false);
35321
+ }
35322
+ if (Math.abs(thetaLength) < Math.PI * 2 - 1e-4) for (const [theta, sign] of [[thetaStart, -1], [thetaStart + thetaLength, 1]]) {
35323
+ const c = Math.cos(theta);
35324
+ const s = Math.sin(theta);
35325
+ const capN = [
35326
+ -s * sign,
35327
+ c * sign,
35328
+ 0
35329
+ ];
35330
+ const p0 = mb.vertex([
35331
+ rInner * c,
35332
+ rInner * s,
35333
+ -hz
35334
+ ], capN, [0, 0]);
35335
+ const p1 = mb.vertex([
35336
+ rOuter * c,
35337
+ rOuter * s,
35040
35338
  -hz
35339
+ ], capN, [1, 0]);
35340
+ const p2 = mb.vertex([
35341
+ rInner * c,
35342
+ rInner * s,
35343
+ hz
35344
+ ], capN, [0, 1]);
35345
+ const p3 = mb.vertex([
35346
+ rOuter * c,
35347
+ rOuter * s,
35348
+ hz
35349
+ ], capN, [1, 1]);
35350
+ if (sign > 0) mb.indices.push(p0, p2, p1, p1, p2, p3);
35351
+ else mb.indices.push(p0, p1, p2, p1, p3, p2);
35352
+ }
35353
+ }
35354
+ }
35355
+ /**
35356
+ * Platonic solids (tetrahedron, octahedron, dodecahedron, icosahedron) and
35357
+ * geodesic icosphere with configurable subdivision `detail`.
35358
+ */
35359
+ function polyhedron(o, mb) {
35360
+ const shape = o.shape ?? "icosahedron";
35361
+ const radius = Math.max(.001, o.radius ?? 50);
35362
+ const detail = Math.max(0, Math.min(5, Math.round(o.detail ?? (shape === "icosphere" ? 2 : 0))));
35363
+ const flatShaded = o.flatShaded ?? shape !== "icosphere";
35364
+ let baseVerts = [];
35365
+ let baseFaces = [];
35366
+ if (shape === "tetrahedron") {
35367
+ baseVerts = [
35368
+ [
35369
+ 1,
35370
+ 1,
35371
+ 1
35041
35372
  ],
35042
35373
  [
35043
- hx,
35044
- -hy,
35045
- -hz
35374
+ -1,
35375
+ -1,
35376
+ 1
35046
35377
  ],
35047
35378
  [
35048
- hx,
35049
- hy,
35050
- -hz
35379
+ -1,
35380
+ 1,
35381
+ -1
35051
35382
  ],
35052
35383
  [
35053
- -hx,
35054
- hy,
35055
- -hz
35384
+ 1,
35385
+ -1,
35386
+ -1
35056
35387
  ]
35057
- ],
35058
- [
35388
+ ];
35389
+ baseFaces = [
35059
35390
  [
35060
- hx,
35061
- -hy,
35062
- hz
35391
+ 2,
35392
+ 1,
35393
+ 0
35063
35394
  ],
35064
35395
  [
35065
- -hx,
35066
- -hy,
35067
- hz
35396
+ 0,
35397
+ 3,
35398
+ 2
35068
35399
  ],
35069
35400
  [
35070
- -hx,
35071
- hy,
35072
- hz
35401
+ 1,
35402
+ 3,
35403
+ 0
35073
35404
  ],
35074
35405
  [
35075
- hx,
35076
- hy,
35077
- hz
35406
+ 2,
35407
+ 3,
35408
+ 1
35078
35409
  ]
35079
- ],
35080
- [
35410
+ ];
35411
+ } else if (shape === "octahedron") {
35412
+ baseVerts = [
35081
35413
  [
35082
- -hx,
35083
- -hy,
35084
- hz
35414
+ 1,
35415
+ 0,
35416
+ 0
35085
35417
  ],
35086
35418
  [
35087
- -hx,
35088
- -hy,
35089
- -hz
35419
+ -1,
35420
+ 0,
35421
+ 0
35090
35422
  ],
35091
35423
  [
35092
- -hx,
35093
- hy,
35094
- -hz
35424
+ 0,
35425
+ 1,
35426
+ 0
35427
+ ],
35428
+ [
35429
+ 0,
35430
+ -1,
35431
+ 0
35432
+ ],
35433
+ [
35434
+ 0,
35435
+ 0,
35436
+ 1
35437
+ ],
35438
+ [
35439
+ 0,
35440
+ 0,
35441
+ -1
35442
+ ]
35443
+ ];
35444
+ baseFaces = [
35445
+ [
35446
+ 0,
35447
+ 2,
35448
+ 4
35449
+ ],
35450
+ [
35451
+ 0,
35452
+ 4,
35453
+ 3
35454
+ ],
35455
+ [
35456
+ 0,
35457
+ 3,
35458
+ 5
35459
+ ],
35460
+ [
35461
+ 0,
35462
+ 5,
35463
+ 2
35464
+ ],
35465
+ [
35466
+ 1,
35467
+ 2,
35468
+ 5
35469
+ ],
35470
+ [
35471
+ 1,
35472
+ 5,
35473
+ 3
35474
+ ],
35475
+ [
35476
+ 1,
35477
+ 3,
35478
+ 4
35479
+ ],
35480
+ [
35481
+ 1,
35482
+ 4,
35483
+ 2
35484
+ ]
35485
+ ];
35486
+ } else if (shape === "dodecahedron") {
35487
+ const t = (1 + Math.sqrt(5)) / 2;
35488
+ const r = 1 / t;
35489
+ baseVerts = [
35490
+ [
35491
+ -1,
35492
+ -1,
35493
+ -1
35494
+ ],
35495
+ [
35496
+ -1,
35497
+ -1,
35498
+ 1
35499
+ ],
35500
+ [
35501
+ -1,
35502
+ 1,
35503
+ -1
35504
+ ],
35505
+ [
35506
+ -1,
35507
+ 1,
35508
+ 1
35509
+ ],
35510
+ [
35511
+ 1,
35512
+ -1,
35513
+ -1
35514
+ ],
35515
+ [
35516
+ 1,
35517
+ -1,
35518
+ 1
35519
+ ],
35520
+ [
35521
+ 1,
35522
+ 1,
35523
+ -1
35524
+ ],
35525
+ [
35526
+ 1,
35527
+ 1,
35528
+ 1
35529
+ ],
35530
+ [
35531
+ 0,
35532
+ -r,
35533
+ -t
35534
+ ],
35535
+ [
35536
+ 0,
35537
+ -r,
35538
+ t
35539
+ ],
35540
+ [
35541
+ 0,
35542
+ r,
35543
+ -t
35544
+ ],
35545
+ [
35546
+ 0,
35547
+ r,
35548
+ t
35549
+ ],
35550
+ [
35551
+ -r,
35552
+ -t,
35553
+ 0
35554
+ ],
35555
+ [
35556
+ -r,
35557
+ t,
35558
+ 0
35559
+ ],
35560
+ [
35561
+ r,
35562
+ -t,
35563
+ 0
35564
+ ],
35565
+ [
35566
+ r,
35567
+ t,
35568
+ 0
35569
+ ],
35570
+ [
35571
+ -t,
35572
+ 0,
35573
+ -r
35574
+ ],
35575
+ [
35576
+ t,
35577
+ 0,
35578
+ -r
35579
+ ],
35580
+ [
35581
+ -t,
35582
+ 0,
35583
+ r
35584
+ ],
35585
+ [
35586
+ t,
35587
+ 0,
35588
+ r
35589
+ ]
35590
+ ];
35591
+ const pentagons = [
35592
+ [
35593
+ 3,
35594
+ 11,
35595
+ 7,
35596
+ 15,
35597
+ 13
35598
+ ],
35599
+ [
35600
+ 7,
35601
+ 19,
35602
+ 17,
35603
+ 6,
35604
+ 15
35605
+ ],
35606
+ [
35607
+ 17,
35608
+ 4,
35609
+ 8,
35610
+ 10,
35611
+ 6
35612
+ ],
35613
+ [
35614
+ 8,
35615
+ 0,
35616
+ 16,
35617
+ 2,
35618
+ 10
35619
+ ],
35620
+ [
35621
+ 0,
35622
+ 12,
35623
+ 1,
35624
+ 18,
35625
+ 16
35626
+ ],
35627
+ [
35628
+ 6,
35629
+ 10,
35630
+ 2,
35631
+ 13,
35632
+ 15
35633
+ ],
35634
+ [
35635
+ 2,
35636
+ 16,
35637
+ 18,
35638
+ 3,
35639
+ 13
35640
+ ],
35641
+ [
35642
+ 18,
35643
+ 1,
35644
+ 9,
35645
+ 11,
35646
+ 3
35647
+ ],
35648
+ [
35649
+ 4,
35650
+ 14,
35651
+ 12,
35652
+ 0,
35653
+ 8
35654
+ ],
35655
+ [
35656
+ 11,
35657
+ 9,
35658
+ 5,
35659
+ 19,
35660
+ 7
35661
+ ],
35662
+ [
35663
+ 19,
35664
+ 5,
35665
+ 14,
35666
+ 4,
35667
+ 17
35668
+ ],
35669
+ [
35670
+ 1,
35671
+ 12,
35672
+ 14,
35673
+ 5,
35674
+ 9
35675
+ ]
35676
+ ];
35677
+ if (detail === 0 && flatShaded) {
35678
+ const unitVerts = baseVerts.map((v) => normalize(v));
35679
+ for (const pent of pentagons) {
35680
+ const pts = pent.map((idx) => unitVerts[idx]);
35681
+ const center = [
35682
+ pts.reduce((s, p) => s + p[0], 0) / 5,
35683
+ pts.reduce((s, p) => s + p[1], 0) / 5,
35684
+ pts.reduce((s, p) => s + p[2], 0) / 5
35685
+ ];
35686
+ let fn = normalize(cross3(sub3(pts[1], pts[0]), sub3(pts[2], pts[0])));
35687
+ if (dot3(fn, center) < 0) {
35688
+ pts.reverse();
35689
+ fn = [
35690
+ -fn[0],
35691
+ -fn[1],
35692
+ -fn[2]
35693
+ ];
35694
+ }
35695
+ const base = mb.positions.length / 3;
35696
+ for (const p of pts) {
35697
+ const u = .5 + Math.atan2(p[2], p[0]) / (2 * Math.PI);
35698
+ const v = Math.acos(Math.max(-1, Math.min(1, -p[1]))) / Math.PI;
35699
+ mb.vertex([
35700
+ p[0] * radius,
35701
+ p[1] * radius,
35702
+ p[2] * radius
35703
+ ], fn, [u, v]);
35704
+ }
35705
+ for (let k = 1; k < pts.length - 1; k++) mb.indices.push(base, base + k, base + k + 1);
35706
+ }
35707
+ return;
35708
+ }
35709
+ for (const p of pentagons) baseFaces.push([
35710
+ p[0],
35711
+ p[1],
35712
+ p[2]
35713
+ ], [
35714
+ p[0],
35715
+ p[2],
35716
+ p[3]
35717
+ ], [
35718
+ p[0],
35719
+ p[3],
35720
+ p[4]
35721
+ ]);
35722
+ } else {
35723
+ const t = (1 + Math.sqrt(5)) / 2;
35724
+ baseVerts = [
35725
+ [
35726
+ -1,
35727
+ t,
35728
+ 0
35729
+ ],
35730
+ [
35731
+ 1,
35732
+ t,
35733
+ 0
35734
+ ],
35735
+ [
35736
+ -1,
35737
+ -t,
35738
+ 0
35739
+ ],
35740
+ [
35741
+ 1,
35742
+ -t,
35743
+ 0
35744
+ ],
35745
+ [
35746
+ 0,
35747
+ -1,
35748
+ t
35749
+ ],
35750
+ [
35751
+ 0,
35752
+ 1,
35753
+ t
35754
+ ],
35755
+ [
35756
+ 0,
35757
+ -1,
35758
+ -t
35759
+ ],
35760
+ [
35761
+ 0,
35762
+ 1,
35763
+ -t
35764
+ ],
35765
+ [
35766
+ t,
35767
+ 0,
35768
+ -1
35769
+ ],
35770
+ [
35771
+ t,
35772
+ 0,
35773
+ 1
35774
+ ],
35775
+ [
35776
+ -t,
35777
+ 0,
35778
+ -1
35779
+ ],
35780
+ [
35781
+ -t,
35782
+ 0,
35783
+ 1
35784
+ ]
35785
+ ];
35786
+ baseFaces = [
35787
+ [
35788
+ 0,
35789
+ 11,
35790
+ 5
35791
+ ],
35792
+ [
35793
+ 0,
35794
+ 5,
35795
+ 1
35796
+ ],
35797
+ [
35798
+ 0,
35799
+ 1,
35800
+ 7
35801
+ ],
35802
+ [
35803
+ 0,
35804
+ 7,
35805
+ 10
35806
+ ],
35807
+ [
35808
+ 0,
35809
+ 10,
35810
+ 11
35811
+ ],
35812
+ [
35813
+ 1,
35814
+ 5,
35815
+ 9
35816
+ ],
35817
+ [
35818
+ 5,
35819
+ 11,
35820
+ 4
35821
+ ],
35822
+ [
35823
+ 11,
35824
+ 10,
35825
+ 2
35826
+ ],
35827
+ [
35828
+ 10,
35829
+ 7,
35830
+ 6
35831
+ ],
35832
+ [
35833
+ 7,
35834
+ 1,
35835
+ 8
35836
+ ],
35837
+ [
35838
+ 3,
35839
+ 9,
35840
+ 4
35841
+ ],
35842
+ [
35843
+ 3,
35844
+ 4,
35845
+ 2
35846
+ ],
35847
+ [
35848
+ 3,
35849
+ 2,
35850
+ 6
35851
+ ],
35852
+ [
35853
+ 3,
35854
+ 6,
35855
+ 8
35856
+ ],
35857
+ [
35858
+ 3,
35859
+ 8,
35860
+ 9
35861
+ ],
35862
+ [
35863
+ 4,
35864
+ 9,
35865
+ 5
35866
+ ],
35867
+ [
35868
+ 2,
35869
+ 4,
35870
+ 11
35871
+ ],
35872
+ [
35873
+ 6,
35874
+ 2,
35875
+ 10
35876
+ ],
35877
+ [
35878
+ 8,
35879
+ 6,
35880
+ 7
35881
+ ],
35882
+ [
35883
+ 9,
35884
+ 8,
35885
+ 1
35886
+ ]
35887
+ ];
35888
+ }
35889
+ const verts = baseVerts.map((v) => normalize(v));
35890
+ let tris = baseFaces.map((f) => [
35891
+ f[0],
35892
+ f[1],
35893
+ f[2]
35894
+ ]);
35895
+ for (let d = 0; d < detail; d++) {
35896
+ const midCache = /* @__PURE__ */ new Map();
35897
+ const midpoint = (a, b) => {
35898
+ const key = a < b ? `${a}_${b}` : `${b}_${a}`;
35899
+ const existing = midCache.get(key);
35900
+ if (existing !== void 0) return existing;
35901
+ const va = verts[a];
35902
+ const vb = verts[b];
35903
+ const idx = verts.length;
35904
+ verts.push(normalize([
35905
+ va[0] + vb[0],
35906
+ va[1] + vb[1],
35907
+ va[2] + vb[2]
35908
+ ]));
35909
+ midCache.set(key, idx);
35910
+ return idx;
35911
+ };
35912
+ const nextTris = [];
35913
+ for (const [i0, i1, i2] of tris) {
35914
+ const m01 = midpoint(i0, i1);
35915
+ const m12 = midpoint(i1, i2);
35916
+ const m20 = midpoint(i2, i0);
35917
+ nextTris.push([
35918
+ i0,
35919
+ m01,
35920
+ m20
35921
+ ], [
35922
+ i1,
35923
+ m12,
35924
+ m01
35925
+ ], [
35926
+ i2,
35927
+ m20,
35928
+ m12
35929
+ ], [
35930
+ m01,
35931
+ m12,
35932
+ m20
35933
+ ]);
35934
+ }
35935
+ tris = nextTris;
35936
+ }
35937
+ const uvOf = (n) => [.5 + Math.atan2(n[2], n[0]) / (2 * Math.PI), Math.acos(Math.max(-1, Math.min(1, -n[1]))) / Math.PI];
35938
+ if (flatShaded) for (const [i0, i1, i2] of tris) {
35939
+ const a = verts[i0];
35940
+ let b = verts[i1];
35941
+ let c = verts[i2];
35942
+ const centroid = [
35943
+ (a[0] + b[0] + c[0]) / 3,
35944
+ (a[1] + b[1] + c[1]) / 3,
35945
+ (a[2] + b[2] + c[2]) / 3
35946
+ ];
35947
+ let fn = normalize(cross3(sub3(b, a), sub3(c, a)));
35948
+ if (dot3(fn, centroid) < 0) {
35949
+ const tmp = b;
35950
+ b = c;
35951
+ c = tmp;
35952
+ fn = [
35953
+ -fn[0],
35954
+ -fn[1],
35955
+ -fn[2]
35956
+ ];
35957
+ }
35958
+ const base = mb.positions.length / 3;
35959
+ mb.vertex([
35960
+ a[0] * radius,
35961
+ a[1] * radius,
35962
+ a[2] * radius
35963
+ ], fn, uvOf(a));
35964
+ mb.vertex([
35965
+ b[0] * radius,
35966
+ b[1] * radius,
35967
+ b[2] * radius
35968
+ ], fn, uvOf(b));
35969
+ mb.vertex([
35970
+ c[0] * radius,
35971
+ c[1] * radius,
35972
+ c[2] * radius
35973
+ ], fn, uvOf(c));
35974
+ mb.indices.push(base, base + 1, base + 2);
35975
+ }
35976
+ else {
35977
+ const base = mb.positions.length / 3;
35978
+ for (const n of verts) mb.vertex([
35979
+ n[0] * radius,
35980
+ n[1] * radius,
35981
+ n[2] * radius
35982
+ ], n, uvOf(n));
35983
+ for (const [i0, i1, i2] of tris) {
35984
+ const a = verts[i0];
35985
+ const b = verts[i1];
35986
+ const c = verts[i2];
35987
+ const centroid = [
35988
+ (a[0] + b[0] + c[0]) / 3,
35989
+ (a[1] + b[1] + c[1]) / 3,
35990
+ (a[2] + b[2] + c[2]) / 3
35991
+ ];
35992
+ if (dot3(cross3(sub3(b, a), sub3(c, a)), centroid) < 0) mb.indices.push(base + i0, base + i2, base + i1);
35993
+ else mb.indices.push(base + i0, base + i1, base + i2);
35994
+ }
35995
+ }
35996
+ }
35997
+ /**
35998
+ * Parses an SVG path string (supporting M, L, H, V, C, S, Q, T, A, Z and multiple subpaths)
35999
+ * into closed 2D contours with adaptive or fixed curve flattening.
36000
+ */
36001
+ function parseSvgPathToContours(d, flattening) {
36002
+ const contours = [];
36003
+ if (!d || typeof d !== "string") return contours;
36004
+ const tokens = [];
36005
+ const regex = /([a-df-z])|([-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?)/gi;
36006
+ let match = regex.exec(d);
36007
+ while (match !== null) {
36008
+ if (match[1]) tokens.push(match[1]);
36009
+ else if (match[2]) tokens.push(Number.parseFloat(match[2]));
36010
+ match = regex.exec(d);
36011
+ }
36012
+ let cursor = 0;
36013
+ let current = {
36014
+ x: 0,
36015
+ y: 0
36016
+ };
36017
+ let subpathStart = {
36018
+ x: 0,
36019
+ y: 0
36020
+ };
36021
+ let lastCubicControl = null;
36022
+ let lastQuadControl = null;
36023
+ let contour = [];
36024
+ const finishContour = () => {
36025
+ if (contour.length >= 2) {
36026
+ const first = contour[0];
36027
+ const last = contour[contour.length - 1];
36028
+ if (Math.hypot(last.x - first.x, last.y - first.y) < 1e-4) contour.pop();
36029
+ if (contour.length >= 2) contours.push(contour);
36030
+ }
36031
+ contour = [];
36032
+ };
36033
+ const segCount = (maxSecondDeriv) => {
36034
+ if (flattening.segments !== void 0) return Math.max(1, flattening.segments);
36035
+ const n = Math.ceil(Math.sqrt(maxSecondDeriv / (8 * Math.max(.001, flattening.tolerance))));
36036
+ return Math.min(64, Math.max(1, n));
36037
+ };
36038
+ const addQuad = (p0, cp, end) => {
36039
+ const n = segCount(2 * Math.hypot(p0.x - 2 * cp.x + end.x, p0.y - 2 * cp.y + end.y));
36040
+ for (let s = 1; s <= n; s++) {
36041
+ const t = s / n;
36042
+ const mt = 1 - t;
36043
+ contour.push({
36044
+ x: mt * mt * p0.x + 2 * mt * t * cp.x + t * t * end.x,
36045
+ y: mt * mt * p0.y + 2 * mt * t * cp.y + t * t * end.y
36046
+ });
36047
+ }
36048
+ };
36049
+ const addCubic = (p0, cp1, cp2, end) => {
36050
+ const n = segCount(6 * Math.max(Math.hypot(p0.x - 2 * cp1.x + cp2.x, p0.y - 2 * cp1.y + cp2.y), Math.hypot(cp1.x - 2 * cp2.x + end.x, cp1.y - 2 * cp2.y + end.y)));
36051
+ for (let s = 1; s <= n; s++) {
36052
+ const t = s / n;
36053
+ const mt = 1 - t;
36054
+ contour.push({
36055
+ x: mt * mt * mt * p0.x + 3 * mt * mt * t * cp1.x + 3 * mt * t * t * cp2.x + t * t * t * end.x,
36056
+ y: mt * mt * mt * p0.y + 3 * mt * mt * t * cp1.y + 3 * mt * t * t * cp2.y + t * t * t * end.y
36057
+ });
36058
+ }
36059
+ };
36060
+ const getNum = () => cursor < tokens.length && typeof tokens[cursor] === "number" ? tokens[cursor++] : 0;
36061
+ while (cursor < tokens.length) {
36062
+ const cmdToken = tokens[cursor++];
36063
+ if (typeof cmdToken !== "string") continue;
36064
+ const isRel = cmdToken === cmdToken.toLowerCase();
36065
+ switch (cmdToken.toUpperCase()) {
36066
+ case "M": {
36067
+ finishContour();
36068
+ let first = true;
36069
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36070
+ let x = getNum();
36071
+ let y = getNum();
36072
+ if (isRel) {
36073
+ x += current.x;
36074
+ y += current.y;
36075
+ }
36076
+ current = {
36077
+ x,
36078
+ y
36079
+ };
36080
+ if (first) {
36081
+ subpathStart = current;
36082
+ contour = [current];
36083
+ first = false;
36084
+ } else contour.push(current);
36085
+ }
36086
+ lastCubicControl = null;
36087
+ lastQuadControl = null;
36088
+ break;
36089
+ }
36090
+ case "L":
36091
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36092
+ let x = getNum();
36093
+ let y = getNum();
36094
+ if (isRel) {
36095
+ x += current.x;
36096
+ y += current.y;
36097
+ }
36098
+ current = {
36099
+ x,
36100
+ y
36101
+ };
36102
+ contour.push(current);
36103
+ }
36104
+ lastCubicControl = null;
36105
+ lastQuadControl = null;
36106
+ break;
36107
+ case "H":
36108
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36109
+ let x = getNum();
36110
+ if (isRel) x += current.x;
36111
+ current = {
36112
+ x,
36113
+ y: current.y
36114
+ };
36115
+ contour.push(current);
36116
+ }
36117
+ lastCubicControl = null;
36118
+ lastQuadControl = null;
36119
+ break;
36120
+ case "V":
36121
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36122
+ let y = getNum();
36123
+ if (isRel) y += current.y;
36124
+ current = {
36125
+ x: current.x,
36126
+ y
36127
+ };
36128
+ contour.push(current);
36129
+ }
36130
+ lastCubicControl = null;
36131
+ lastQuadControl = null;
36132
+ break;
36133
+ case "C":
36134
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36135
+ let x1 = getNum();
36136
+ let y1 = getNum();
36137
+ let x2 = getNum();
36138
+ let y2 = getNum();
36139
+ let x = getNum();
36140
+ let y = getNum();
36141
+ if (isRel) {
36142
+ x1 += current.x;
36143
+ y1 += current.y;
36144
+ x2 += current.x;
36145
+ y2 += current.y;
36146
+ x += current.x;
36147
+ y += current.y;
36148
+ }
36149
+ const cp1 = {
36150
+ x: x1,
36151
+ y: y1
36152
+ };
36153
+ const cp2 = {
36154
+ x: x2,
36155
+ y: y2
36156
+ };
36157
+ const end = {
36158
+ x,
36159
+ y
36160
+ };
36161
+ addCubic(current, cp1, cp2, end);
36162
+ current = end;
36163
+ lastCubicControl = cp2;
36164
+ }
36165
+ lastQuadControl = null;
36166
+ break;
36167
+ case "S":
36168
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36169
+ let x2 = getNum();
36170
+ let y2 = getNum();
36171
+ let x = getNum();
36172
+ let y = getNum();
36173
+ if (isRel) {
36174
+ x2 += current.x;
36175
+ y2 += current.y;
36176
+ x += current.x;
36177
+ y += current.y;
36178
+ }
36179
+ const cp1 = lastCubicControl ? {
36180
+ x: 2 * current.x - lastCubicControl.x,
36181
+ y: 2 * current.y - lastCubicControl.y
36182
+ } : current;
36183
+ const cp2 = {
36184
+ x: x2,
36185
+ y: y2
36186
+ };
36187
+ const end = {
36188
+ x,
36189
+ y
36190
+ };
36191
+ addCubic(current, cp1, cp2, end);
36192
+ current = end;
36193
+ lastCubicControl = cp2;
36194
+ }
36195
+ lastQuadControl = null;
36196
+ break;
36197
+ case "Q":
36198
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36199
+ let x1 = getNum();
36200
+ let y1 = getNum();
36201
+ let x = getNum();
36202
+ let y = getNum();
36203
+ if (isRel) {
36204
+ x1 += current.x;
36205
+ y1 += current.y;
36206
+ x += current.x;
36207
+ y += current.y;
36208
+ }
36209
+ const cp = {
36210
+ x: x1,
36211
+ y: y1
36212
+ };
36213
+ const end = {
36214
+ x,
36215
+ y
36216
+ };
36217
+ addQuad(current, cp, end);
36218
+ current = end;
36219
+ lastQuadControl = cp;
36220
+ }
36221
+ lastCubicControl = null;
36222
+ break;
36223
+ case "T":
36224
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36225
+ let x = getNum();
36226
+ let y = getNum();
36227
+ if (isRel) {
36228
+ x += current.x;
36229
+ y += current.y;
36230
+ }
36231
+ const cp = lastQuadControl ? {
36232
+ x: 2 * current.x - lastQuadControl.x,
36233
+ y: 2 * current.y - lastQuadControl.y
36234
+ } : current;
36235
+ const end = {
36236
+ x,
36237
+ y
36238
+ };
36239
+ addQuad(current, cp, end);
36240
+ current = end;
36241
+ lastQuadControl = cp;
36242
+ }
36243
+ lastCubicControl = null;
36244
+ break;
36245
+ case "A":
36246
+ while (cursor < tokens.length && typeof tokens[cursor] === "number") {
36247
+ const rx = getNum();
36248
+ const ry = getNum();
36249
+ const rot = getNum();
36250
+ const largeArc = getNum() !== 0;
36251
+ const sweep = getNum() !== 0;
36252
+ let x = getNum();
36253
+ let y = getNum();
36254
+ if (isRel) {
36255
+ x += current.x;
36256
+ y += current.y;
36257
+ }
36258
+ const end = {
36259
+ x,
36260
+ y
36261
+ };
36262
+ const segs = sampleSvgArc(current, rx, ry, rot, largeArc, sweep, end);
36263
+ for (const s of segs) contour.push(s.p1);
36264
+ current = end;
36265
+ }
36266
+ lastCubicControl = null;
36267
+ lastQuadControl = null;
36268
+ break;
36269
+ case "Z":
36270
+ finishContour();
36271
+ current = subpathStart;
36272
+ lastCubicControl = null;
36273
+ lastQuadControl = null;
36274
+ break;
36275
+ }
36276
+ }
36277
+ finishContour();
36278
+ return contours;
36279
+ }
36280
+ function toPoint2D(p) {
36281
+ return Array.isArray(p) ? {
36282
+ x: p[0],
36283
+ y: p[1]
36284
+ } : {
36285
+ x: p.x,
36286
+ y: p.y
36287
+ };
36288
+ }
36289
+ function normalizeContoursInput(o, flattening) {
36290
+ const raw = o.shape ?? o.d ?? o.contour;
36291
+ let contours = [];
36292
+ if (typeof raw === "string") contours = parseSvgPathToContours(raw, flattening).filter((c) => c.length >= 3);
36293
+ else if (Array.isArray(raw) && raw.length > 0) {
36294
+ const first = raw[0];
36295
+ if (Array.isArray(first) && first.length > 0 && (Array.isArray(first[0]) || typeof first[0] === "object" && first[0] !== null && "x" in first[0])) contours = raw.map((c) => c.map(toPoint2D)).filter((c) => c.length >= 3);
36296
+ else {
36297
+ const single = raw.map(toPoint2D);
36298
+ if (single.length >= 3) contours = [single];
36299
+ }
36300
+ }
36301
+ if (Array.isArray(o.holes)) for (const hole of o.holes) {
36302
+ const mapped = hole.map(toPoint2D);
36303
+ if (mapped.length >= 3) contours.push(mapped);
36304
+ }
36305
+ return contours;
36306
+ }
36307
+ /** Side-wall segments meeting at more than 30° keep a crisp hard edge. */
36308
+ const SIDE_CREASE_COS = Math.cos(30 * Math.PI / 180);
36309
+ /**
36310
+ * Extrudes any 2D outline (SVG path string with subpaths/holes, or polygon contour arrays)
36311
+ * into a 3D solid mesh with optional front/back bevels and crease-aware smooth/hard side normals.
36312
+ */
36313
+ function buildExtrudedShapeModel(o) {
36314
+ const halfDepth = Math.max(.001, o.depth ?? 20) / 2;
36315
+ const bevelThickness = Math.max(0, Math.min(halfDepth * .49, o.bevelThickness ?? 0));
36316
+ const bevelSize = Math.max(0, o.bevelSize ?? bevelThickness);
36317
+ const bevelSegments = bevelThickness > 0 || bevelSize > 0 ? Math.max(1, Math.round(o.bevelSegments ?? 2)) : 0;
36318
+ const polygons = organizeGlyphPolygons({
36319
+ contours: normalizeContoursInput(o, {
36320
+ segments: o.curveSegments,
36321
+ tolerance: Math.max(.001, o.curveTolerance ?? .25)
36322
+ }),
36323
+ simplified: false
36324
+ });
36325
+ let minX = Number.POSITIVE_INFINITY;
36326
+ let maxX = Number.NEGATIVE_INFINITY;
36327
+ let minY = Number.POSITIVE_INFINITY;
36328
+ let maxY = Number.NEGATIVE_INFINITY;
36329
+ for (const poly of polygons) {
36330
+ for (const p of poly.outer) {
36331
+ if (p.x < minX) minX = p.x;
36332
+ if (p.x > maxX) maxX = p.x;
36333
+ if (p.y < minY) minY = p.y;
36334
+ if (p.y > maxY) maxY = p.y;
36335
+ }
36336
+ for (const hole of poly.holes) for (const p of hole) {
36337
+ if (p.x < minX) minX = p.x;
36338
+ if (p.x > maxX) maxX = p.x;
36339
+ if (p.y < minY) minY = p.y;
36340
+ if (p.y > maxY) maxY = p.y;
36341
+ }
36342
+ }
36343
+ if (!Number.isFinite(minX)) minX = maxX = minY = maxY = 0;
36344
+ if (o.center ?? true) {
36345
+ const cx = (minX + maxX) / 2;
36346
+ const cy = (minY + maxY) / 2;
36347
+ for (const poly of polygons) {
36348
+ for (const p of poly.outer) {
36349
+ p.x -= cx;
36350
+ p.y -= cy;
36351
+ }
36352
+ for (const hole of poly.holes) for (const p of hole) {
36353
+ p.x -= cx;
36354
+ p.y -= cy;
36355
+ }
36356
+ }
36357
+ minX -= cx;
36358
+ maxX -= cx;
36359
+ minY -= cy;
36360
+ maxY -= cy;
36361
+ }
36362
+ const w = maxX - minX;
36363
+ const h = maxY - minY;
36364
+ const frontMb = new MeshBuilder();
36365
+ const sideMb = new MeshBuilder();
36366
+ const useSeparateSideMaterial = o.bevelColor !== void 0 && o.bevelColor !== o.color;
36367
+ const wallMb = useSeparateSideMaterial ? sideMb : frontMb;
36368
+ const cleanContour = (contour) => {
36369
+ const pts = contour.filter((p, k) => {
36370
+ const next = contour[(k + 1) % contour.length];
36371
+ return Math.hypot(next.x - p.x, next.y - p.y) >= 1e-5;
36372
+ });
36373
+ const count = pts.length;
36374
+ if (count < 3) return null;
36375
+ const edgeLengths = [];
36376
+ const edgeNormals = pts.map((p, k) => {
36377
+ const next = pts[(k + 1) % count];
36378
+ const len = Math.hypot(next.x - p.x, next.y - p.y);
36379
+ edgeLengths.push(len);
36380
+ return {
36381
+ x: (next.y - p.y) / len,
36382
+ y: -(next.x - p.x) / len
36383
+ };
36384
+ });
36385
+ return {
36386
+ pts,
36387
+ edgeLengths,
36388
+ edgeNormals,
36389
+ miterNormals: pts.map((_, k) => {
36390
+ const nPrev = edgeNormals[(k - 1 + count) % count];
36391
+ const nNext = edgeNormals[k];
36392
+ const sx = nPrev.x + nNext.x;
36393
+ const sy = nPrev.y + nNext.y;
36394
+ const len = Math.hypot(sx, sy);
36395
+ if (len < 1e-6) return nNext;
36396
+ const dot = sx / len * nNext.x + sy / len * nNext.y;
36397
+ const scale = Math.min(2, 1 / Math.max(.35, dot));
36398
+ return {
36399
+ x: sx / len * scale,
36400
+ y: sy / len * scale
36401
+ };
36402
+ })
36403
+ };
36404
+ };
36405
+ const addSideWallWithProfile = (contour) => {
36406
+ const info = cleanContour(contour);
36407
+ if (!info) return;
36408
+ const { pts, edgeLengths, edgeNormals, miterNormals } = info;
36409
+ const count = pts.length;
36410
+ const perimeter = edgeLengths.reduce((a, b) => a + b, 0) || 1;
36411
+ const slices = [];
36412
+ if (bevelSegments <= 0) slices.push({
36413
+ z: -halfDepth,
36414
+ inset: 0,
36415
+ nr: 1,
36416
+ nz: 0,
36417
+ v: 0
36418
+ }, {
36419
+ z: halfDepth,
36420
+ inset: 0,
36421
+ nr: 1,
36422
+ nz: 0,
36423
+ v: 1
36424
+ });
36425
+ else {
36426
+ const totalRows = bevelSegments * 2 + 1;
36427
+ let row = 0;
36428
+ for (let b = 0; b <= bevelSegments; b++) {
36429
+ const a = b / bevelSegments * Math.PI / 2;
36430
+ slices.push({
36431
+ z: -halfDepth + bevelThickness * (1 - Math.cos(a)),
36432
+ inset: -bevelSize * (1 - Math.sin(a)),
36433
+ nr: Math.sin(a),
36434
+ nz: -Math.cos(a),
36435
+ v: row++ / totalRows
36436
+ });
36437
+ }
36438
+ for (let b = 0; b <= bevelSegments; b++) {
36439
+ const a = b / bevelSegments * Math.PI / 2;
36440
+ slices.push({
36441
+ z: halfDepth - bevelThickness * (1 - Math.sin(a)),
36442
+ inset: -bevelSize * (1 - Math.cos(a)),
36443
+ nr: Math.cos(a),
36444
+ nz: Math.sin(a),
36445
+ v: row++ / totalRows
36446
+ });
36447
+ }
36448
+ }
36449
+ const numSlices = slices.length;
36450
+ const addColumn = (p, miter, n2d, u) => {
36451
+ const base = wallMb.positions.length / 3;
36452
+ for (const s of slices) {
36453
+ const px = p.x + miter.x * s.inset;
36454
+ const py = p.y + miter.y * s.inset;
36455
+ const n3 = normalize([
36456
+ n2d.x * s.nr,
36457
+ n2d.y * s.nr,
36458
+ s.nz
36459
+ ]);
36460
+ wallMb.vertex([
36461
+ px,
36462
+ py,
36463
+ s.z
36464
+ ], n3, [u, s.v]);
36465
+ }
36466
+ return base;
36467
+ };
36468
+ const startColumn = [];
36469
+ const endColumn = [];
36470
+ let along = 0;
36471
+ for (let k = 0; k < count; k++) {
36472
+ const incoming = edgeNormals[(k - 1 + count) % count];
36473
+ const outgoing = edgeNormals[k];
36474
+ const miter = miterNormals[k];
36475
+ const u = along / perimeter;
36476
+ const sx = incoming.x + outgoing.x;
36477
+ const sy = incoming.y + outgoing.y;
36478
+ const averaged = incoming.x * outgoing.x + incoming.y * outgoing.y >= SIDE_CREASE_COS ? {
36479
+ x: sx / Math.hypot(sx, sy),
36480
+ y: sy / Math.hypot(sx, sy)
36481
+ } : null;
36482
+ if (averaged && k > 0) startColumn[k] = endColumn[k] = addColumn(pts[k], miter, averaged, u);
36483
+ else {
36484
+ endColumn[k] = addColumn(pts[k], miter, averaged ?? incoming, k === 0 ? 1 : u);
36485
+ startColumn[k] = addColumn(pts[k], miter, averaged ?? outgoing, u);
36486
+ }
36487
+ along += edgeLengths[k];
36488
+ }
36489
+ for (let k = 0; k < count; k++) {
36490
+ const colA = startColumn[k];
36491
+ const colB = endColumn[(k + 1) % count];
36492
+ for (let s = 0; s < numSlices - 1; s++) {
36493
+ const a0 = colA + s;
36494
+ const a1 = colA + s + 1;
36495
+ const b0 = colB + s;
36496
+ const b1 = colB + s + 1;
36497
+ wallMb.indices.push(a0, b0, a1, b0, b1, a1);
36498
+ }
36499
+ }
36500
+ };
36501
+ for (const poly of polygons) {
36502
+ const outer = poly.outer;
36503
+ if (computeContourSignedArea(outer) < 0) outer.reverse();
36504
+ for (const hole of poly.holes) if (computeContourSignedArea(hole) > 0) hole.reverse();
36505
+ const outerInfo = cleanContour(outer);
36506
+ if (!outerInfo) continue;
36507
+ const holeInfos = poly.holes.map(cleanContour).filter((x) => x !== null);
36508
+ const capInset = -bevelSize;
36509
+ const earcutCoords = [];
36510
+ const holeIndices = [];
36511
+ for (let k = 0; k < outerInfo.pts.length; k++) {
36512
+ const p = outerInfo.pts[k];
36513
+ const m = outerInfo.miterNormals[k];
36514
+ earcutCoords.push(p.x + m.x * capInset, p.y + m.y * capInset);
36515
+ }
36516
+ for (const hInfo of holeInfos) {
36517
+ holeIndices.push(earcutCoords.length / 2);
36518
+ for (let k = 0; k < hInfo.pts.length; k++) {
36519
+ const p = hInfo.pts[k];
36520
+ const m = hInfo.miterNormals[k];
36521
+ earcutCoords.push(p.x + m.x * capInset, p.y + m.y * capInset);
36522
+ }
36523
+ }
36524
+ const triangles = earcut(earcutCoords, holeIndices, 2);
36525
+ const vertCount = earcutCoords.length / 2;
36526
+ const frontBase = frontMb.positions.length / 3;
36527
+ for (let v = 0; v < vertCount; v++) {
36528
+ const vx = earcutCoords[v * 2];
36529
+ const vy = earcutCoords[v * 2 + 1];
36530
+ frontMb.vertex([
36531
+ vx,
36532
+ vy,
36533
+ -halfDepth
36534
+ ], [
36535
+ 0,
36536
+ 0,
36537
+ -1
36538
+ ], [(vx - minX) / (w || 1), (vy - minY) / (h || 1)]);
36539
+ }
36540
+ for (let t = 0; t < triangles.length; t += 3) frontMb.indices.push(frontBase + triangles[t], frontBase + triangles[t + 2], frontBase + triangles[t + 1]);
36541
+ const backBase = frontMb.positions.length / 3;
36542
+ for (let v = 0; v < vertCount; v++) {
36543
+ const vx = earcutCoords[v * 2];
36544
+ const vy = earcutCoords[v * 2 + 1];
36545
+ frontMb.vertex([
36546
+ vx,
36547
+ vy,
36548
+ halfDepth
36549
+ ], [
36550
+ 0,
36551
+ 0,
36552
+ 1
36553
+ ], [(vx - minX) / (w || 1), (vy - minY) / (h || 1)]);
36554
+ }
36555
+ for (let t = 0; t < triangles.length; t += 3) frontMb.indices.push(backBase + triangles[t], backBase + triangles[t + 1], backBase + triangles[t + 2]);
36556
+ addSideWallWithProfile(outer);
36557
+ for (const hole of poly.holes) addSideWallWithProfile(hole);
36558
+ }
36559
+ const meshes = [];
36560
+ const materials = {};
36561
+ const frontMesh = frontMb.build("extrude-front");
36562
+ frontMesh.materialName = "extrude-front-material";
36563
+ meshes.push(frontMesh);
36564
+ materials["extrude-front-material"] = {
36565
+ name: "extrude-front-material",
36566
+ diffuseColor: parseHexOrRgbColor(o.color ?? "#ffffff"),
36567
+ shading: o.material ?? "lit",
36568
+ shininess: o.shininess ?? 32,
36569
+ roughness: o.roughness,
36570
+ metallic: o.metallic
36571
+ };
36572
+ if (useSeparateSideMaterial && sideMb.indices.length > 0) {
36573
+ const sideMesh = sideMb.build("extrude-side");
36574
+ sideMesh.materialName = "extrude-side-material";
36575
+ meshes.push(sideMesh);
36576
+ materials["extrude-side-material"] = {
36577
+ name: "extrude-side-material",
36578
+ diffuseColor: parseHexOrRgbColor(o.bevelColor ?? o.color ?? "#ffffff"),
36579
+ shading: o.material ?? "lit",
36580
+ shininess: o.shininess ?? 32,
36581
+ roughness: o.roughness,
36582
+ metallic: o.metallic
36583
+ };
36584
+ }
36585
+ return finalizeModel3DData("extrude", meshes, materials);
36586
+ }
36587
+ /**
36588
+ * Revolves a 2D profile `(r, y)` around the Y axis (`x = r cos φ, y = y, z = r sin φ`).
36589
+ */
36590
+ function lathe(o, mb) {
36591
+ const rawPoints = typeof o.points === "string" ? parseSvgPathToContours(o.points, { tolerance: .25 })[0] ?? [] : o.points.map(toPoint2D);
36592
+ if (rawPoints.length < 2) return;
36593
+ const pts = rawPoints.map((p) => ({
36594
+ x: Math.max(0, p.x),
36595
+ y: p.y
36596
+ }));
36597
+ const isClosedLoop = pts.length >= 3 && Math.hypot(pts[pts.length - 1].x - pts[0].x, pts[pts.length - 1].y - pts[0].y) < 1e-4;
36598
+ if (isClosedLoop) {
36599
+ if (computeContourSignedArea(pts) < 0) pts.reverse();
36600
+ } else if (pts[0].y > pts[pts.length - 1].y) pts.reverse();
36601
+ const seg = Math.max(3, Math.round(o.segments ?? 32));
36602
+ const phiStart = (o.phiStart ?? 0) * Math.PI / 180;
36603
+ const phiLength = (o.phiLength ?? 360) * Math.PI / 180;
36604
+ const count = pts.length;
36605
+ const segLens = [];
36606
+ const segNormals = [];
36607
+ let totalLen = 0;
36608
+ for (let j = 0; j < count - 1; j++) {
36609
+ const a = pts[j];
36610
+ const b = pts[j + 1];
36611
+ const dr = b.x - a.x;
36612
+ const dy = b.y - a.y;
36613
+ const len = Math.hypot(dr, dy);
36614
+ segLens.push(len);
36615
+ totalLen += len;
36616
+ segNormals.push(len > 1e-8 ? {
36617
+ x: dy / len,
36618
+ y: -dr / len
36619
+ } : {
36620
+ x: 1,
36621
+ y: 0
36622
+ });
36623
+ }
36624
+ const vCoords = [0];
36625
+ let acc = 0;
36626
+ for (const len of segLens) {
36627
+ acc += len;
36628
+ vCoords.push(totalLen > 0 ? acc / totalLen : 0);
36629
+ }
36630
+ if (o.flatShaded) for (let j = 0; j < count - 1; j++) {
36631
+ const pA = pts[j];
36632
+ const pB = pts[j + 1];
36633
+ const n2 = segNormals[j];
36634
+ for (let i = 0; i < seg; i++) {
36635
+ const phi0 = phiStart + i / seg * phiLength;
36636
+ const phi1 = phiStart + (i + 1) / seg * phiLength;
36637
+ const phiMid = (phi0 + phi1) / 2;
36638
+ const fn = normalize([
36639
+ n2.x * Math.cos(phiMid),
36640
+ n2.y,
36641
+ n2.x * Math.sin(phiMid)
36642
+ ]);
36643
+ const c0 = Math.cos(phi0);
36644
+ const s0 = Math.sin(phi0);
36645
+ const c1 = Math.cos(phi1);
36646
+ const s1 = Math.sin(phi1);
36647
+ const base = mb.positions.length / 3;
36648
+ mb.vertex([
36649
+ pA.x * c0,
36650
+ pA.y,
36651
+ pA.x * s0
36652
+ ], fn, [i / seg, vCoords[j]]);
36653
+ mb.vertex([
36654
+ pA.x * c1,
36655
+ pA.y,
36656
+ pA.x * s1
36657
+ ], fn, [(i + 1) / seg, vCoords[j]]);
36658
+ mb.vertex([
36659
+ pB.x * c0,
36660
+ pB.y,
36661
+ pB.x * s0
36662
+ ], fn, [i / seg, vCoords[j + 1]]);
36663
+ mb.vertex([
36664
+ pB.x * c1,
36665
+ pB.y,
36666
+ pB.x * s1
36667
+ ], fn, [(i + 1) / seg, vCoords[j + 1]]);
36668
+ mb.indices.push(base, base + 2, base + 1, base + 1, base + 2, base + 3);
36669
+ }
36670
+ }
36671
+ else {
36672
+ const smoothOrCrease = (nSeg, nAdj) => {
36673
+ if (nSeg.x * nAdj.x + nSeg.y * nAdj.y < SIDE_CREASE_COS) return nSeg;
36674
+ const nx = nSeg.x + nAdj.x;
36675
+ const ny = nSeg.y + nAdj.y;
36676
+ const nLen = Math.hypot(nx, ny);
36677
+ return nLen > 1e-8 ? {
36678
+ x: nx / nLen,
36679
+ y: ny / nLen
36680
+ } : nSeg;
36681
+ };
36682
+ const emitRing = (p, n2, v) => {
36683
+ for (let i = 0; i <= seg; i++) {
36684
+ const phi = phiStart + i / seg * phiLength;
36685
+ const c = Math.cos(phi);
36686
+ const s = Math.sin(phi);
36687
+ const vNorm = p.x <= 1e-5 && Math.abs(n2.y) > 1e-5 ? [
36688
+ 0,
36689
+ n2.y < 0 ? -1 : 1,
36690
+ 0
36691
+ ] : normalize([
36692
+ n2.x * c,
36693
+ n2.y,
36694
+ n2.x * s
36695
+ ]);
36696
+ mb.vertex([
36697
+ p.x * c,
36698
+ p.y,
36699
+ p.x * s
36700
+ ], vNorm, [i / seg, v]);
36701
+ }
36702
+ };
36703
+ for (let j = 0; j < count - 1; j++) {
36704
+ const pA = pts[j];
36705
+ const pB = pts[j + 1];
36706
+ const nSeg = segNormals[j];
36707
+ const nPrevA = j > 0 ? segNormals[j - 1] : isClosedLoop ? segNormals[count - 2] : nSeg;
36708
+ const nNextB = j + 1 < count - 1 ? segNormals[j + 1] : isClosedLoop ? segNormals[0] : nSeg;
36709
+ const nA = smoothOrCrease(nSeg, nPrevA);
36710
+ const nB = smoothOrCrease(nSeg, nNextB);
36711
+ const base = mb.positions.length / 3;
36712
+ emitRing(pA, nA, vCoords[j]);
36713
+ emitRing(pB, nB, vCoords[j + 1]);
36714
+ mb.grid(base, seg, 1, false);
36715
+ }
36716
+ }
36717
+ if ((o.capped ?? o.closed) && !isClosedLoop) {
36718
+ const first = pts[0];
36719
+ const last = pts[count - 1];
36720
+ for (const [pt, ny] of [[first, -1], [last, 1]]) {
36721
+ if (pt.x <= 1e-5) continue;
36722
+ const center = mb.vertex([
36723
+ 0,
36724
+ pt.y,
36725
+ 0
36726
+ ], [
36727
+ 0,
36728
+ ny,
36729
+ 0
36730
+ ], [.5, .5]);
36731
+ const ring$1 = mb.positions.length / 3;
36732
+ for (let i = 0; i <= seg; i++) {
36733
+ const phi = phiStart + i / seg * phiLength;
36734
+ const c = Math.cos(phi);
36735
+ const s = Math.sin(phi);
36736
+ mb.vertex([
36737
+ pt.x * c,
36738
+ pt.y,
36739
+ pt.x * s
36740
+ ], [
36741
+ 0,
36742
+ ny,
36743
+ 0
36744
+ ], [(c + 1) / 2, (s + 1) / 2]);
36745
+ }
36746
+ for (let i = 0; i < seg; i++) if (ny < 0) mb.indices.push(center, ring$1 + i, ring$1 + i + 1);
36747
+ else mb.indices.push(center, ring$1 + i + 1, ring$1 + i);
36748
+ }
36749
+ }
36750
+ }
36751
+ function buildPath3DFromSpec(spec, closedDefault = false) {
36752
+ if (Array.isArray(spec)) {
36753
+ if (spec.length < 2) throw new Error("3D path requires at least 2 points");
36754
+ return Path3D.catmullRom(spec, { closed: closedDefault });
36755
+ }
36756
+ if (spec.kind === "catmullRom") return Path3D.catmullRom(spec.points, {
36757
+ tension: spec.tension,
36758
+ closed: spec.closed ?? closedDefault,
36759
+ samplesPerSegment: spec.samplesPerSegment
36760
+ });
36761
+ if (spec.kind === "bezier") return Path3D.bezier(spec.segments, spec.samplesPerSegment);
36762
+ if (spec.kind === "helix") return Path3D.helix({
36763
+ center: spec.center ?? [
36764
+ 0,
36765
+ 0,
36766
+ 0
36767
+ ],
36768
+ radius: spec.radius,
36769
+ pitch: spec.pitch,
36770
+ turns: spec.turns,
36771
+ axis: spec.axis,
36772
+ samplesPerTurn: spec.samplesPerTurn
36773
+ });
36774
+ return Path3D.circle({
36775
+ center: spec.center ?? [
36776
+ 0,
36777
+ 0,
36778
+ 0
36779
+ ],
36780
+ radius: spec.radius,
36781
+ startAngleDeg: spec.startAngleDeg,
36782
+ endAngleDeg: spec.endAngleDeg,
36783
+ samples: spec.samples
36784
+ });
36785
+ }
36786
+ /**
36787
+ * Sweeps a circular (or thick-walled hollow pipe) cross-section along a 3D path
36788
+ * using Rotation-Minimizing Frames (RMF) so the tube never kinks or gimbal-flips.
36789
+ */
36790
+ function tube(o, mb) {
36791
+ const closed = o.closed ?? (!Array.isArray(o.path) && o.path.kind === "catmullRom" && Boolean(o.path.closed));
36792
+ const path$1 = buildPath3DFromSpec(o.path, closed);
36793
+ const tubSeg = Math.max(2, Math.round(o.tubularSegments ?? 64));
36794
+ const radSeg = Math.max(3, Math.round(o.radialSegments ?? 16));
36795
+ const rDefault = Math.max(.001, o.radius ?? 12);
36796
+ const rStart = o.radiusTaper ? Math.max(0, o.radiusTaper[0]) : rDefault;
36797
+ const rEnd = o.radiusTaper ? Math.max(0, o.radiusTaper[1]) : rDefault;
36798
+ const rInnerBase = Math.max(0, Math.min(rDefault - .001, o.innerRadius ?? 0));
36799
+ const capped = o.capped ?? true;
36800
+ const frames = [];
36801
+ for (let j = 0; j <= tubSeg; j++) frames.push(path$1.getPointAt(j / tubSeg));
36802
+ const outerBase = mb.positions.length / 3;
36803
+ for (let j = 0; j <= tubSeg; j++) {
36804
+ const u = j / tubSeg;
36805
+ const r = rStart + (rEnd - rStart) * u;
36806
+ const f = frames[j];
36807
+ const [px, py, pz] = f.position;
36808
+ const [nx, ny, nz] = f.normal;
36809
+ const [bx, by, bz] = f.binormal;
36810
+ for (let i = 0; i <= radSeg; i++) {
36811
+ const v = i / radSeg;
36812
+ const theta = v * Math.PI * 2;
36813
+ const c = Math.cos(theta);
36814
+ const s = Math.sin(theta);
36815
+ const dirX = c * nx + s * bx;
36816
+ const dirY = c * ny + s * by;
36817
+ const dirZ = c * nz + s * bz;
36818
+ mb.vertex([
36819
+ px + r * dirX,
36820
+ py + r * dirY,
36821
+ pz + r * dirZ
36822
+ ], normalize([
36823
+ dirX,
36824
+ dirY,
36825
+ dirZ
36826
+ ]), [u, v]);
36827
+ }
36828
+ }
36829
+ mb.grid(outerBase, radSeg, tubSeg, true);
36830
+ if (rInnerBase > 1e-6) {
36831
+ const innerRatio = rInnerBase / rDefault;
36832
+ const innerBase = mb.positions.length / 3;
36833
+ for (let j = 0; j <= tubSeg; j++) {
36834
+ const u = j / tubSeg;
36835
+ const r = (rStart + (rEnd - rStart) * u) * innerRatio;
36836
+ const f = frames[j];
36837
+ const [px, py, pz] = f.position;
36838
+ const [nx, ny, nz] = f.normal;
36839
+ const [bx, by, bz] = f.binormal;
36840
+ for (let i = 0; i <= radSeg; i++) {
36841
+ const v = i / radSeg;
36842
+ const theta = v * Math.PI * 2;
36843
+ const c = Math.cos(theta);
36844
+ const s = Math.sin(theta);
36845
+ const dirX = c * nx + s * bx;
36846
+ const dirY = c * ny + s * by;
36847
+ const dirZ = c * nz + s * bz;
36848
+ mb.vertex([
36849
+ px + r * dirX,
36850
+ py + r * dirY,
36851
+ pz + r * dirZ
36852
+ ], normalize([
36853
+ -dirX,
36854
+ -dirY,
36855
+ -dirZ
36856
+ ]), [u, v]);
36857
+ }
36858
+ }
36859
+ mb.grid(innerBase, radSeg, tubSeg, false);
36860
+ if (!closed && capped) for (const [j, sign] of [[0, -1], [tubSeg, 1]]) {
36861
+ const u = j / tubSeg;
36862
+ const rOut = rStart + (rEnd - rStart) * u;
36863
+ const rIn = rOut * innerRatio;
36864
+ const f = frames[j];
36865
+ const [px, py, pz] = f.position;
36866
+ const [nx, ny, nz] = f.normal;
36867
+ const [bx, by, bz] = f.binormal;
36868
+ const capN = normalize([
36869
+ f.tangent[0] * sign,
36870
+ f.tangent[1] * sign,
36871
+ f.tangent[2] * sign
36872
+ ]);
36873
+ const base = mb.positions.length / 3;
36874
+ for (const rr of [rIn, rOut]) for (let i = 0; i <= radSeg; i++) {
36875
+ const theta = i / radSeg * Math.PI * 2;
36876
+ const c = Math.cos(theta);
36877
+ const s = Math.sin(theta);
36878
+ const dirX = c * nx + s * bx;
36879
+ const dirY = c * ny + s * by;
36880
+ const dirZ = c * nz + s * bz;
36881
+ mb.vertex([
36882
+ px + rr * dirX,
36883
+ py + rr * dirY,
36884
+ pz + rr * dirZ
36885
+ ], capN, [(c * (rr / rOut) + 1) / 2, (s * (rr / rOut) + 1) / 2]);
36886
+ }
36887
+ mb.grid(base, radSeg, 1, sign < 0);
36888
+ }
36889
+ } else if (!closed && capped) for (const [j, sign] of [[0, -1], [tubSeg, 1]]) {
36890
+ const u = j / tubSeg;
36891
+ const r = rStart + (rEnd - rStart) * u;
36892
+ if (r <= 1e-6) continue;
36893
+ const f = frames[j];
36894
+ const [px, py, pz] = f.position;
36895
+ const [nx, ny, nz] = f.normal;
36896
+ const [bx, by, bz] = f.binormal;
36897
+ const capN = normalize([
36898
+ f.tangent[0] * sign,
36899
+ f.tangent[1] * sign,
36900
+ f.tangent[2] * sign
36901
+ ]);
36902
+ const center = mb.vertex([
36903
+ px,
36904
+ py,
36905
+ pz
36906
+ ], capN, [.5, .5]);
36907
+ const ring$1 = mb.positions.length / 3;
36908
+ for (let i = 0; i <= radSeg; i++) {
36909
+ const theta = i / radSeg * Math.PI * 2;
36910
+ const c = Math.cos(theta);
36911
+ const s = Math.sin(theta);
36912
+ const dirX = c * nx + s * bx;
36913
+ const dirY = c * ny + s * by;
36914
+ const dirZ = c * nz + s * bz;
36915
+ mb.vertex([
36916
+ px + r * dirX,
36917
+ py + r * dirY,
36918
+ pz + r * dirZ
36919
+ ], capN, [(c + 1) / 2, (s + 1) / 2]);
36920
+ }
36921
+ for (let i = 0; i < radSeg; i++) if (sign > 0) mb.indices.push(center, ring$1 + i, ring$1 + i + 1);
36922
+ else mb.indices.push(center, ring$1 + i + 1, ring$1 + i);
36923
+ }
36924
+ }
36925
+ function resolvePointArray(spec) {
36926
+ if (Array.isArray(spec)) return spec;
36927
+ const path$1 = buildPath3DFromSpec(spec);
36928
+ const count = 96;
36929
+ const out = [];
36930
+ for (let i = 0; i <= count; i++) out.push(path$1.getPointAt(i / count).position);
36931
+ return out;
36932
+ }
36933
+ /**
36934
+ * Native 3D line geometry (`line-list` topology) with optional dashing and per-vertex colors.
36935
+ */
36936
+ function line3d(o, mb) {
36937
+ mb.topology = "line-list";
36938
+ const pts = resolvePointArray(o.points);
36939
+ if (pts.length < 2) return;
36940
+ const parsedColors = o.colors?.map((c) => parseHexOrRgbColor(c));
36941
+ const mode = o.mode ?? "strip";
36942
+ if (o.dashSize && o.dashSize > 0 && mode === "strip") {
36943
+ const dash = o.dashSize;
36944
+ const cycle = dash + (o.gapSize ?? dash);
36945
+ const workPts = o.closed ? [...pts, pts[0]] : pts;
36946
+ let dist = 0;
36947
+ for (let i = 0; i < workPts.length - 1; i++) {
36948
+ const a = workPts[i];
36949
+ const b = workPts[i + 1];
36950
+ const segLen = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
36951
+ if (segLen < 1e-6) continue;
36952
+ const dir = [
36953
+ (b[0] - a[0]) / segLen,
36954
+ (b[1] - a[1]) / segLen,
36955
+ (b[2] - a[2]) / segLen
36956
+ ];
36957
+ const colA = parsedColors?.[i % parsedColors.length];
36958
+ let local = 0;
36959
+ while (local < segLen) {
36960
+ const phase = (dist + local) % cycle;
36961
+ if (phase < dash) {
36962
+ const step = Math.min(dash - phase, segLen - local);
36963
+ const p0 = [
36964
+ a[0] + dir[0] * local,
36965
+ a[1] + dir[1] * local,
36966
+ a[2] + dir[2] * local
36967
+ ];
36968
+ const p1 = [
36969
+ a[0] + dir[0] * (local + step),
36970
+ a[1] + dir[1] * (local + step),
36971
+ a[2] + dir[2] * (local + step)
36972
+ ];
36973
+ const idx0 = mb.vertex(p0, [
36974
+ 0,
36975
+ -1,
36976
+ 0
36977
+ ], [0, 0], colA);
36978
+ const idx1 = mb.vertex(p1, [
36979
+ 0,
36980
+ -1,
36981
+ 0
36982
+ ], [1, 0], colA);
36983
+ mb.indices.push(idx0, idx1);
36984
+ local += step;
36985
+ } else local += cycle - phase;
36986
+ }
36987
+ dist += segLen;
36988
+ }
36989
+ return;
36990
+ }
36991
+ for (let i = 0; i < pts.length; i++) {
36992
+ const u = pts.length > 1 ? i / (pts.length - 1) : 0;
36993
+ mb.vertex(pts[i], [
36994
+ 0,
36995
+ -1,
36996
+ 0
36997
+ ], [u, 0], parsedColors ? parsedColors[i % parsedColors.length] : void 0);
36998
+ }
36999
+ if (mode === "segments") for (let i = 0; i + 1 < pts.length; i += 2) mb.indices.push(i, i + 1);
37000
+ else {
37001
+ for (let i = 0; i < pts.length - 1; i++) mb.indices.push(i, i + 1);
37002
+ if (o.closed && pts.length >= 3) mb.indices.push(pts.length - 1, 0);
37003
+ }
37004
+ }
37005
+ /**
37006
+ * 3D point cloud geometry: either native GPU `point-list` topology (`pointSize <= 0` or omitted)
37007
+ * or icosahedral 3D dots of radius `pointSize` for diagram vertices that scale with perspective.
37008
+ */
37009
+ function points3d(o, mb) {
37010
+ const pts = resolvePointArray(o.points);
37011
+ if (pts.length === 0) return;
37012
+ const parsedColors = o.colors?.map((c) => parseHexOrRgbColor(c));
37013
+ const pointSize = o.pointSize ?? 0;
37014
+ if (pointSize > 0) {
37015
+ const dotMb = new MeshBuilder();
37016
+ polyhedron({
37017
+ type: "polyhedron",
37018
+ shape: "icosphere",
37019
+ radius: pointSize,
37020
+ detail: 1,
37021
+ flatShaded: false
37022
+ }, dotMb);
37023
+ const dotVerts = dotMb.positions.length / 3;
37024
+ for (let pIdx = 0; pIdx < pts.length; pIdx++) {
37025
+ const [px, py, pz] = pts[pIdx];
37026
+ const col = parsedColors ? parsedColors[pIdx % parsedColors.length] : void 0;
37027
+ const base = mb.positions.length / 3;
37028
+ for (let v = 0; v < dotVerts; v++) mb.vertex([
37029
+ px + dotMb.positions[v * 3],
37030
+ py + dotMb.positions[v * 3 + 1],
37031
+ pz + dotMb.positions[v * 3 + 2]
37032
+ ], [
37033
+ dotMb.normals[v * 3],
37034
+ dotMb.normals[v * 3 + 1],
37035
+ dotMb.normals[v * 3 + 2]
37036
+ ], [dotMb.uvs[v * 2], dotMb.uvs[v * 2 + 1]], col);
37037
+ for (const idx of dotMb.indices) mb.indices.push(base + idx);
37038
+ }
37039
+ return;
37040
+ }
37041
+ mb.topology = "point-list";
37042
+ for (let i = 0; i < pts.length; i++) {
37043
+ const u = pts.length > 1 ? i / (pts.length - 1) : 0;
37044
+ const idx = mb.vertex(pts[i], [
37045
+ 0,
37046
+ -1,
37047
+ 0
37048
+ ], [u, 0], parsedColors ? parsedColors[i % parsedColors.length] : void 0);
37049
+ mb.indices.push(idx);
37050
+ }
37051
+ }
37052
+ function shard(o, mb) {
37053
+ const r = o.radius ?? 50;
37054
+ const hx = (o.size ? o.size[0] : r) || 50;
37055
+ const hy = (o.size ? o.size[1] : r) || 50;
37056
+ const hz = (o.size ? o.size[2] : r) || 50;
37057
+ const maxR = Math.max(hx, hy, hz);
37058
+ const facets = Math.max(4, Math.min(64, Math.round(o.facets ?? 14)));
37059
+ const seed = Math.round(o.seed ?? 1) >>> 0;
37060
+ const irregularity = Math.max(0, Math.min(.85, o.irregularity ?? .35));
37061
+ let state = BigInt(seed) * 6364136223846793005n + 1442695040888963407n;
37062
+ const rnd = () => {
37063
+ state = state * 6364136223846793005n + 1442695040888963407n;
37064
+ const xorshifted = Number((state >> 18n ^ state) >> 27n) >>> 0;
37065
+ const rot = Number(state >> 59n) >>> 0;
37066
+ return ((xorshifted >>> rot | xorshifted << (32 - rot & 31)) >>> 0) / 4294967296;
37067
+ };
37068
+ let faces = [
37069
+ [
37070
+ [
37071
+ -hx,
37072
+ -hy,
37073
+ -hz
37074
+ ],
37075
+ [
37076
+ hx,
37077
+ -hy,
37078
+ -hz
37079
+ ],
37080
+ [
37081
+ hx,
37082
+ hy,
37083
+ -hz
37084
+ ],
37085
+ [
37086
+ -hx,
37087
+ hy,
37088
+ -hz
37089
+ ]
37090
+ ],
37091
+ [
37092
+ [
37093
+ hx,
37094
+ -hy,
37095
+ hz
37096
+ ],
37097
+ [
37098
+ -hx,
37099
+ -hy,
37100
+ hz
37101
+ ],
37102
+ [
37103
+ -hx,
37104
+ hy,
37105
+ hz
37106
+ ],
37107
+ [
37108
+ hx,
37109
+ hy,
37110
+ hz
37111
+ ]
37112
+ ],
37113
+ [
37114
+ [
37115
+ -hx,
37116
+ -hy,
37117
+ hz
37118
+ ],
37119
+ [
37120
+ -hx,
37121
+ -hy,
37122
+ -hz
37123
+ ],
37124
+ [
37125
+ -hx,
37126
+ hy,
37127
+ -hz
35095
37128
  ],
35096
37129
  [
35097
37130
  -hx,
@@ -35330,24 +37363,7 @@ function shard(o, mb) {
35330
37363
  for (let j = 1; j < face.length - 1; j++) mb.indices.push(baseIdx, baseIdx + j, baseIdx + j + 1);
35331
37364
  }
35332
37365
  }
35333
- /** Builds a procedural solid as a single-mesh Model3DData. */
35334
- function generatePrimitiveGeometry(options) {
35335
- const mb = new MeshBuilder();
35336
- if (options.type === "roundedBox") roundedBox(options, mb);
35337
- else if (options.type === "sphere") sphere(options, mb);
35338
- else if (options.type === "cylinder") cylinder(options, mb);
35339
- else if (options.type === "shard") shard(options, mb);
35340
- else throw new Error(`Unknown 3D primitive: ${options.type}`);
35341
- const name = options.type;
35342
- const mesh = mb.build(name);
35343
- const material = {
35344
- name: `${name}-material`,
35345
- diffuseColor: parseHexOrRgbColor(options.color ?? "#ffffff"),
35346
- shading: options.material ?? "lit",
35347
- shininess: options.shininess ?? 32,
35348
- roughness: options.roughness,
35349
- metallic: options.metallic
35350
- };
37366
+ function finalizeModel3DData(name, meshes, materials) {
35351
37367
  const min = [
35352
37368
  Number.POSITIVE_INFINITY,
35353
37369
  Number.POSITIVE_INFINITY,
@@ -35358,11 +37374,17 @@ function generatePrimitiveGeometry(options) {
35358
37374
  Number.NEGATIVE_INFINITY,
35359
37375
  Number.NEGATIVE_INFINITY
35360
37376
  ];
35361
- const pos = mesh.positions;
35362
- for (let i = 0; i < pos.length; i += 3) for (let k = 0; k < 3; k++) {
35363
- const v = pos[i + k];
35364
- if (v < min[k]) min[k] = v;
35365
- if (v > max[k]) max[k] = v;
37377
+ for (const mesh of meshes) {
37378
+ const pos = mesh.positions;
37379
+ for (let i = 0; i < pos.length; i += 3) for (let k = 0; k < 3; k++) {
37380
+ const v = pos[i + k];
37381
+ if (v < min[k]) min[k] = v;
37382
+ if (v > max[k]) max[k] = v;
37383
+ }
37384
+ }
37385
+ if (!Number.isFinite(min[0])) {
37386
+ min[0] = min[1] = min[2] = 0;
37387
+ max[0] = max[1] = max[2] = 0;
35366
37388
  }
35367
37389
  const size = [
35368
37390
  max[0] - min[0],
@@ -35371,8 +37393,8 @@ function generatePrimitiveGeometry(options) {
35371
37393
  ];
35372
37394
  return {
35373
37395
  name: `primitive-${name}`,
35374
- meshes: [mesh],
35375
- materials: { [material.name]: material },
37396
+ meshes,
37397
+ materials,
35376
37398
  animations: [],
35377
37399
  bounds: {
35378
37400
  min,
@@ -35387,6 +37409,36 @@ function generatePrimitiveGeometry(options) {
35387
37409
  }
35388
37410
  };
35389
37411
  }
37412
+ /** Builds a procedural solid as a Model3DData. */
37413
+ function generatePrimitiveGeometry(options) {
37414
+ if (options.type === "extrude") return buildExtrudedShapeModel(options);
37415
+ const mb = new MeshBuilder();
37416
+ if (options.type === "roundedBox") roundedBox(options, mb);
37417
+ else if (options.type === "sphere") sphere(options, mb);
37418
+ else if (options.type === "cylinder") cylinder(options, mb);
37419
+ else if (options.type === "torus") torus(options, mb);
37420
+ else if (options.type === "capsule") capsule(options, mb);
37421
+ else if (options.type === "ring") ring(options, mb);
37422
+ else if (options.type === "polyhedron") polyhedron(options, mb);
37423
+ else if (options.type === "lathe") lathe(options, mb);
37424
+ else if (options.type === "tube") tube(options, mb);
37425
+ else if (options.type === "line3d") line3d(options, mb);
37426
+ else if (options.type === "points3d") points3d(options, mb);
37427
+ else if (options.type === "shard") shard(options, mb);
37428
+ else throw new Error(`Unknown 3D primitive: ${options.type}`);
37429
+ const name = options.type;
37430
+ const mesh = mb.build(name);
37431
+ const defaultShading = options.type === "line3d" || options.type === "points3d" && (options.pointSize ?? 0) <= 0 ? "unlit" : "lit";
37432
+ const material = {
37433
+ name: `${name}-material`,
37434
+ diffuseColor: parseHexOrRgbColor(options.color ?? "#ffffff"),
37435
+ shading: options.material ?? defaultShading,
37436
+ shininess: options.shininess ?? 32,
37437
+ roughness: options.roughness,
37438
+ metallic: options.metallic
37439
+ };
37440
+ return finalizeModel3DData(name, [mesh], { [material.name]: material });
37441
+ }
35390
37442
  const primitiveCache = /* @__PURE__ */ new Map();
35391
37443
  /**
35392
37444
  * Like generatePrimitiveGeometry, but identical options return the same
@@ -37084,6 +39136,7 @@ var Renderer3D = class {
37084
39136
  dofBindGroupLayout;
37085
39137
  cameraPool;
37086
39138
  lightsPool;
39139
+ directLightsPool;
37087
39140
  instancePool;
37088
39141
  cameraUniformBindGroupCache = new UniformBindGroupCache();
37089
39142
  samplerCache = new SamplerCache();
@@ -37108,6 +39161,7 @@ var Renderer3D = class {
37108
39161
  modelCache = /* @__PURE__ */ new Map();
37109
39162
  lastCameraPose = null;
37110
39163
  shadowTexture = null;
39164
+ shadowTextures = /* @__PURE__ */ new Map();
37111
39165
  placeholderShadowTexture = null;
37112
39166
  shadowSampler = null;
37113
39167
  meshBounds = /* @__PURE__ */ new WeakMap();
@@ -37132,6 +39186,7 @@ var Renderer3D = class {
37132
39186
  this.floorReflectionPipeline = new FloorReflectionPipeline(device, format, sampleCount, this.quad3dPipeline.cameraLayout, this.quad3dPipeline.lightsLayout, "depth24plus");
37133
39187
  this.cameraPool = new BufferPool(320, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
37134
39188
  this.lightsPool = new BufferPool(LIGHT_UNIFORM_SIZE_BYTES, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
39189
+ this.directLightsPool = new BufferPool(64, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
37135
39190
  this.instancePool = new BufferPool(65536, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
37136
39191
  const dofModule = device.createShaderModule({
37137
39192
  label: "dof-bokeh.wgsl",
@@ -37183,6 +39238,7 @@ var Renderer3D = class {
37183
39238
  this.lastCameraPose = null;
37184
39239
  this.lastCameraBuffer = null;
37185
39240
  this.lightsPool.reset();
39241
+ this.directLightsPool.reset();
37186
39242
  this.instancePool.reset();
37187
39243
  this.quad3dPipeline.resetPools();
37188
39244
  this.slug3dPipeline.resetPools();
@@ -37367,11 +39423,14 @@ var Renderer3D = class {
37367
39423
  return this.cameraUniformBindGroupCache.getBindGroup(this.device, this.quad3dPipeline.cameraLayout, cameraBuffer);
37368
39424
  }
37369
39425
  getOrCreateLightsBuffer(lights, world) {
37370
- const lightsData = Light3D.packLightsUniforms(lights ?? [], world?.environment, world?.shadow);
39426
+ const lightsData = Light3D.packLightsUniforms(lights ?? [], world?.environment, Array.isArray(world?.shadow) ? world.shadow[0] : world?.shadow);
37371
39427
  return this.lightsPool.getBuffer(this.device, lightsData);
37372
39428
  }
37373
39429
  /** Lights uniform + shadow map + comparison sampler (group 3 of every lit shader). */
37374
- createLightsBindGroup(lightsBuffer, withShadow) {
39430
+ createLightsBindGroup(lightsBuffer, withShadow, lights, shadows) {
39431
+ const data = Light3D.packDirectLights(lights, shadows);
39432
+ if (data.byteLength > this.device.limits.maxStorageBufferBindingSize) throw new Error(`Direct lights require ${data.byteLength} bytes, exceeding the device storage binding limit of ${this.device.limits.maxStorageBufferBindingSize} bytes`);
39433
+ const directBuffer = this.directLightsPool.getBuffer(this.device, data);
37375
39434
  this.shadowSampler ??= this.device.createSampler({
37376
39435
  label: "ShadowComparisonSampler",
37377
39436
  compare: "less-equal",
@@ -37400,11 +39459,15 @@ var Renderer3D = class {
37400
39459
  },
37401
39460
  {
37402
39461
  binding: 1,
37403
- resource: map.createView()
39462
+ resource: map.createView({ dimension: "2d-array" })
37404
39463
  },
37405
39464
  {
37406
39465
  binding: 2,
37407
39466
  resource: this.shadowSampler
39467
+ },
39468
+ {
39469
+ binding: 3,
39470
+ resource: { buffer: directBuffer }
37408
39471
  }
37409
39472
  ]
37410
39473
  });
@@ -37487,6 +39550,9 @@ var Renderer3D = class {
37487
39550
  processed.dirRadius[2]
37488
39551
  ];
37489
39552
  const mapSize = options.mapSize ?? 2048;
39553
+ const layer = options.layer ?? 0;
39554
+ const layerCount = options.layerCount ?? 1;
39555
+ if (layerCount > this.device.limits.maxTextureArrayLayers) throw new Error(`Shadow maps require ${layerCount} texture layers, exceeding the device limit of ${this.device.limits.maxTextureArrayLayers}`);
37490
39556
  const casterBounds = casters.map((c) => this.worldBounds(c));
37491
39557
  const frustum = fitShadowFrustum({
37492
39558
  type: kind,
@@ -37495,18 +39561,22 @@ var Renderer3D = class {
37495
39561
  angle: light.angle
37496
39562
  }, casterBounds, receivers, mapSize);
37497
39563
  if (!frustum) return null;
37498
- if (!this.shadowTexture || this.shadowTexture.width !== mapSize) {
37499
- this.shadowTexture?.destroy();
37500
- this.shadowTexture = this.device.createTexture({
39564
+ const key = `${mapSize}:${layerCount}`;
39565
+ let texture = this.shadowTextures.get(key);
39566
+ if (!texture) {
39567
+ texture = this.device.createTexture({
37501
39568
  label: "ShadowMap",
37502
39569
  size: {
37503
39570
  width: mapSize,
37504
- height: mapSize
39571
+ height: mapSize,
39572
+ depthOrArrayLayers: layerCount
37505
39573
  },
37506
39574
  format: SHADOW_DEPTH_FORMAT,
37507
39575
  usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
37508
39576
  });
39577
+ this.shadowTextures.set(key, texture);
37509
39578
  }
39579
+ this.shadowTexture = texture;
37510
39580
  const lightCam = new Float32Array(80);
37511
39581
  lightCam.set(frustum.viewProj, 32);
37512
39582
  lightCam.set(frustum.viewProj, 0);
@@ -37525,7 +39595,11 @@ var Renderer3D = class {
37525
39595
  label: "ShadowMapPass",
37526
39596
  colorAttachments: [],
37527
39597
  depthStencilAttachment: {
37528
- view: this.shadowTexture.createView(),
39598
+ view: this.shadowTexture.createView({
39599
+ dimension: "2d",
39600
+ baseArrayLayer: layer,
39601
+ arrayLayerCount: 1
39602
+ }),
37529
39603
  depthClearValue: 1,
37530
39604
  depthLoadOp: "clear",
37531
39605
  depthStoreOp: "store"
@@ -37576,7 +39650,7 @@ var Renderer3D = class {
37576
39650
  const cameraBuffer = this.packCameraUniforms(camera, surfaceWidth, surfaceHeight);
37577
39651
  const cameraBindGroup = this.createCameraBindGroup(cameraBuffer);
37578
39652
  const lightsBuffer = this.getOrCreateLightsBuffer(lights, world);
37579
- const lightsBindGroup = this.createLightsBindGroup(lightsBuffer, Boolean(world?.shadow));
39653
+ const lightsBindGroup = this.createLightsBindGroup(lightsBuffer, Boolean(world?.shadow), lights ?? [], world?.shadow ? Array.isArray(world.shadow) ? world.shadow : [world.shadow] : []);
37580
39654
  const msaa = this.sampleCount > 1;
37581
39655
  if (msaa && loadOp === "load") throw new Error("Renderer3D.beginPass: loadOp 'load' needs a Renderer3D built with sampleCount 1");
37582
39656
  const attach = (view, msaaTex, clearValue, attachmentLoadOp) => msaaTex ? {
@@ -37636,9 +39710,9 @@ var Renderer3D = class {
37636
39710
  const depthTex = this.getOrCreateReflectionDepthTexture(reflWidth, reflHeight);
37637
39711
  const cameraBuffer = this.packCameraUniforms(camera, reflWidth, reflHeight);
37638
39712
  const cameraBindGroup = this.createCameraBindGroup(cameraBuffer);
37639
- const lightsData = Light3D.packLightsUniforms(lights ?? [], world?.environment, world?.shadow, true);
39713
+ const lightsData = Light3D.packLightsUniforms(lights ?? [], world?.environment, Array.isArray(world?.shadow) ? world.shadow[0] : world?.shadow, true);
37640
39714
  const lightsBuffer = this.lightsPool.getBuffer(this.device, lightsData);
37641
- const lightsBindGroup = this.createLightsBindGroup(lightsBuffer, Boolean(world?.shadow));
39715
+ const lightsBindGroup = this.createLightsBindGroup(lightsBuffer, Boolean(world?.shadow), lights ?? [], world?.shadow ? Array.isArray(world.shadow) ? world.shadow : [world.shadow] : []);
37642
39716
  const msaa = this.sampleCount > 1;
37643
39717
  const msaaTex = msaa ? this.getOrCreateReflectionMsaaTexture(reflWidth, reflHeight) : null;
37644
39718
  const colorAttachments = [msaaTex ? {
@@ -38061,7 +40135,8 @@ var Renderer3D = class {
38061
40135
  matte: opts.matte,
38062
40136
  maps: opts.maps,
38063
40137
  isInstanced: Boolean(opts.instanceBuffer || opts.instanceCount && opts.instanceCount > 1),
38064
- instanceBuffer: opts.instanceBuffer
40138
+ instanceBuffer: opts.instanceBuffer,
40139
+ matrixInstances: opts.matrixInstances
38065
40140
  }, useMrt, this.passWritesHdr, opts.instanceCount ?? 1, opts.firstInstance ?? 0);
38066
40141
  }
38067
40142
  loadModel(srcOrData, format = "auto", options = {}) {
@@ -38265,7 +40340,8 @@ var Renderer3D = class {
38265
40340
  destroy() {
38266
40341
  this.bloomPipeline?.destroy();
38267
40342
  this.depthTexture?.destroy();
38268
- this.shadowTexture?.destroy();
40343
+ for (const texture of this.shadowTextures.values()) texture.destroy();
40344
+ this.shadowTextures.clear();
38269
40345
  this.placeholderShadowTexture?.destroy();
38270
40346
  this.linearDepthTexture?.destroy();
38271
40347
  this.msaaColorTexture?.destroy();
@@ -38284,6 +40360,7 @@ var Renderer3D = class {
38284
40360
  this.reflectionBlurredTexture?.destroy();
38285
40361
  this.cameraPool.destroy();
38286
40362
  this.lightsPool.destroy();
40363
+ this.directLightsPool.destroy();
38287
40364
  this.instancePool.destroy();
38288
40365
  this.cameraUniformBindGroupCache.destroy();
38289
40366
  }
@@ -38934,4 +41011,4 @@ if (typeof globalThis !== "undefined") globalThis.__GATEWAI_DELAYS__ = globalThi
38934
41011
 
38935
41012
  //#endregion
38936
41013
  export { probeVideoSize as $, TextPathBuilder as $t, emojiGraphemes as A, generateArcLengthLUT as An, CubicBezierSegmentSchema as At, jointMatrices as B, ParticleDissolveConfigSchema as Bt, drawGifNode as C, evaluateExpressionSelector as Cn, AdvancedTextAnimatorSchema as Ct, drawSignalNode as D, evaluateSpringProgress as Dn, AudioSignalComputePipeline as Dt, drawParagraphNode as E, evaluateSkewMatrix as En, AnimatableGlyphPropsSchema as Et, getPrimitiveGeometry as F, sampleAudioSpectrumScale as Fn, GlyphAnchorGroupingSchema as Ft, measureMaxCaptionHeight as G, SignalRegistry as Gt, loadModel3D as H, SelectorModeSchema as Ht, getRegisteredFontSource as I, sampleCurveGeometry as In, GlyphAnchorPointSchema as It, parseFBX as J, SpringPhysicsSchema as Jt, mediaDecoderCache as K, SkewPropertiesSchema as Kt, getRenderer2D as L, samplePathChainGeometry as Ln, KineticMarqueeConfigSchema as Lt, fitSpansToText as M, getScramblePool as Mn, EnhancedTextNodeSchema as Mt, generateExtrudedTextGeometry as N, getShuffledOrder as Nn, ExpressionSelectorSchema as Nt, drawSvgNode as O, evaluateTypewriterState as On, AudioSpectrumTypographySchema as Ot, getHeadlessFontPath as P, parseSvgPathToCubicBeziers as Pn, ExtendedGlyphPropertiesSchema as Pt, poseNodes as Q, TextCursorStyleSchema as Qt, initHeadlessWebGPU as R, signalRegistry as Rn, LineSpacingDynamicsSchema as Rt, drawCaptionNode as S, evaluateCubicBezierDerivative as Sn, AdvancedTextAnimatorBuilder as St, drawLottieNode as T, evaluateSelectorCombination as Tn, AnchorPresetSchema as Tt, loadOBJ as U, SelectorShapeSchema as Ut, loadFBX as V, SelectorBasedOnSchema as Vt, lutStore as W, SelectorUnitsSchema as Wt, parseOBJ as X, TextAnimatorBuilder as Xt, parseMTL as Y, TextAnchorConfigSchema as Yt, parseSvgPathToSegments as Z, TextCursorConfigSchema as Zt, buildShapeSvgPath as _, computeRangeSelectorWeight as _n, Vector3Math as _t, GlassPipeline as a, TextSignalSelectorSchema as an, spansText as at, colorMatrixWgsl as b, ellipseToCubicBeziers as bn, resolveEnvironment as bt, Renderer2D as c, TypingCadenceSchema as cn, GetFontAssetUrl as ct, ScreenSpaceRelightPipeline as d, VolumetricFormationConfigSchema as dn, ENVIRONMENT_PRESETS as dt, TextPathOptionsSchema as en, registerHeadlessFont as et, ShadowPipeline as f, VolumetricFormationModeSchema as fn, LIGHT_UNIFORM_SIZE_BYTES as ft, WebGPUAudioProcessor as g, computeLineLeadingDisplacement as gn, Path3D as gt, SlugPipeline as h, computeAnchorPivotOffset as hn, Matrix4Math as ht, EffectPipeline as i, TextSelectorSchema as in, shaderStore as it, ensureDevice as j, generatePathSegments as jn, DynamicStrokePropertiesSchema as jt, drawVideoNode as k, evaluateVolumetricFormation as kn, ChromaticGlitchConfigSchema as kt, Renderer3D as l, VariableFontAxesSchema as ln, resolveMediaSourceUrl as lt, SlugGeometry as m, buildWGSLSignalFn as mn, MAX_LIGHTS as mt, AudioLatentTrackCache as n, TextRangeSelectorSchema as nn, sampleMorphWeights as nt, MotionBlurPipeline as o, TextWigglySelectorSchema as on, srtLoader as ot, SlugFontCache as p, buildPathLUT as pn, Light3D as pt, parseColor as q, Spatial3DPropertiesSchema as qt, AudioMeshDeformPipeline as r, TextScrambleConfigSchema as rn, setRegisteredFontSource as rt, NodeSurfaceProvider as s, TypewriterAnimatorSchema as sn, textureCache as st, AudioLatentEngine as t, TextPathSourceSchema as tn, resolveTextSpans as tt, SSAOPipeline as u, Vec2Schema as un, Camera3D as ut, clearAllVideoCache as v, deterministicWigglyNoise as vn, linearRgb as vt, drawImageNode as w, evaluateMarqueeOffset as wn, AnchorGroupingSchema as wt, describeGpu as x, evaluateCubicBezier as xn, AETextAnimatorSchema as xt, clearHeadlessFonts as y, distanceToCurveParameter as yn, parseColorToRgb as yt, inputStore as z, waveToCubicBeziers as zn, LongShadowConfigSchema as zt };
38937
- //# sourceMappingURL=dist-CnFcSau0.mjs.map
41014
+ //# sourceMappingURL=dist-CFe1fC9l.mjs.map