framefields 2.3.2 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/README.md +24 -1
  2. package/dist/audio/index.d.mts +4 -4
  3. package/dist/audio/index.mjs +10 -10
  4. package/dist/{audio-BNnEclHU.d.mts → audio-C6toT7F3.d.mts} +3 -3
  5. package/dist/{audio-BNnEclHU.d.mts.map → audio-C6toT7F3.d.mts.map} +1 -1
  6. package/dist/{audio-C7Kn5xBm.mjs → audio-DXwNhi1z.mjs} +3 -3
  7. package/dist/{audio-C7Kn5xBm.mjs.map → audio-DXwNhi1z.mjs.map} +1 -1
  8. package/dist/{dist-CPUIfrtw.mjs → dist-Cl0mvShy.mjs} +2 -2
  9. package/dist/{dist-CPUIfrtw.mjs.map → dist-Cl0mvShy.mjs.map} +1 -1
  10. package/dist/{dist-CturBK2Z.mjs → dist-DRB6Vg5u.mjs} +1188 -743
  11. package/dist/dist-DRB6Vg5u.mjs.map +1 -0
  12. package/dist/effects/index.d.mts +4 -4
  13. package/dist/effects/index.mjs +34 -34
  14. package/dist/{effects-CW1VgdI5.mjs → effects-lBqG-vd1.mjs} +2 -2
  15. package/dist/{effects-CW1VgdI5.mjs.map → effects-lBqG-vd1.mjs.map} +1 -1
  16. package/dist/fonts/index.mjs +2 -2
  17. package/dist/{fonts-EgMNL8KE.mjs → fonts-DlRJD6NS.mjs} +2 -2
  18. package/dist/{fonts-EgMNL8KE.mjs.map → fonts-DlRJD6NS.mjs.map} +1 -1
  19. package/dist/{gpu-CN14e7Bi.mjs → gpu-DOQX1W2q.mjs} +2 -2
  20. package/dist/{gpu-CN14e7Bi.mjs.map → gpu-DOQX1W2q.mjs.map} +1 -1
  21. package/dist/{index-QmkcUL3W.d.mts → index-BHqdtJbN.d.mts} +2 -2
  22. package/dist/{index-QmkcUL3W.d.mts.map → index-BHqdtJbN.d.mts.map} +1 -1
  23. package/dist/{index-CeRv1KC0.d.mts → index-BONyHjZX.d.mts} +3 -3
  24. package/dist/{index-CeRv1KC0.d.mts.map → index-BONyHjZX.d.mts.map} +1 -1
  25. package/dist/{index-Cg2ptXcs.d.mts → index-CaiZ5p6q.d.mts} +2 -2
  26. package/dist/{index-Cg2ptXcs.d.mts.map → index-CaiZ5p6q.d.mts.map} +1 -1
  27. package/dist/{index-BU51pxZO.d.mts → index-Cf1pVxn0.d.mts} +4 -4
  28. package/dist/{index-BU51pxZO.d.mts.map → index-Cf1pVxn0.d.mts.map} +1 -1
  29. package/dist/{index-AdrZIWT0.d.mts → index-DEODKaL6.d.mts} +13 -1
  30. package/dist/index-DEODKaL6.d.mts.map +1 -0
  31. package/dist/{index-B3BwLHa6.d.mts → index-DN9oXO-X.d.mts} +3 -3
  32. package/dist/{index-B3BwLHa6.d.mts.map → index-DN9oXO-X.d.mts.map} +1 -1
  33. package/dist/index.d.mts +177 -9
  34. package/dist/index.d.mts.map +1 -1
  35. package/dist/index.mjs +485 -90
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/preview/index.d.mts +2 -2
  38. package/dist/preview/index.mjs +10 -10
  39. package/dist/{preview-1sjnhHQL.mjs → preview-Ca4tF5Ro.mjs} +2 -2
  40. package/dist/{preview-1sjnhHQL.mjs.map → preview-Ca4tF5Ro.mjs.map} +1 -1
  41. package/dist/preview-engine/chunk-7QJUC6YY.mjs +1525 -0
  42. package/dist/preview-engine/framefields.mjs +32 -25
  43. package/dist/preview-engine/player.mjs +1 -1
  44. package/dist/preview-engine/{renderers-WP2LBR5U.mjs → renderers-HQEKSUNR.mjs} +3 -3
  45. package/dist/{program-DtiNMTxO.mjs → program-CJEg2wMa.mjs} +2 -2
  46. package/dist/{program-DtiNMTxO.mjs.map → program-CJEg2wMa.mjs.map} +1 -1
  47. package/dist/renderer/index.d.mts +2 -2
  48. package/dist/renderer/index.mjs +2 -2
  49. package/dist/renderer--IHe-yqK.mjs +5 -0
  50. package/dist/{renderer-CMzd3BrW.mjs → renderer-445i0uxs.mjs} +2 -2
  51. package/dist/{renderer-CMzd3BrW.mjs.map → renderer-445i0uxs.mjs.map} +1 -1
  52. package/dist/{renderer-MS70wrQB.mjs → renderer-5tQ7ImPk.mjs} +2 -2
  53. package/dist/{renderer-MS70wrQB.mjs.map → renderer-5tQ7ImPk.mjs.map} +1 -1
  54. package/dist/{renderer-Dw1e5phO.mjs → renderer-Ai5nZsof.mjs} +3 -3
  55. package/dist/{renderer-Dw1e5phO.mjs.map → renderer-Ai5nZsof.mjs.map} +1 -1
  56. package/dist/renderer-B16iwR0_.mjs +5 -0
  57. package/dist/renderer-B3EEYpx6.mjs +5 -0
  58. package/dist/renderer-B3Si5IB4.mjs +5 -0
  59. package/dist/{renderer-ktpusVT2.mjs → renderer-B4JmNa_T.mjs} +3 -3
  60. package/dist/{renderer-ktpusVT2.mjs.map → renderer-B4JmNa_T.mjs.map} +1 -1
  61. package/dist/{renderer-CBG6ewNf.mjs → renderer-B6UqWjtH.mjs} +3 -3
  62. package/dist/{renderer-CBG6ewNf.mjs.map → renderer-B6UqWjtH.mjs.map} +1 -1
  63. package/dist/{renderer-BmWH4dK1.mjs → renderer-B8v8iEkB.mjs} +7 -6
  64. package/dist/{renderer-BmWH4dK1.mjs.map → renderer-B8v8iEkB.mjs.map} +1 -1
  65. package/dist/{renderer-dJcu7WqI.mjs → renderer-BFWRUTov.mjs} +3 -3
  66. package/dist/{renderer-dJcu7WqI.mjs.map → renderer-BFWRUTov.mjs.map} +1 -1
  67. package/dist/renderer-BH8vZKmX.mjs +5 -0
  68. package/dist/renderer-BHZie6Pa.mjs +5 -0
  69. package/dist/renderer-BHpJ1e15.mjs +5 -0
  70. package/dist/{renderer-CRYO5HQw.mjs → renderer-BL36f2IQ.mjs} +2 -2
  71. package/dist/{renderer-CRYO5HQw.mjs.map → renderer-BL36f2IQ.mjs.map} +1 -1
  72. package/dist/{renderer-Bq9jXYJ5.mjs → renderer-BOvCstdU.mjs} +3 -3
  73. package/dist/{renderer-Bq9jXYJ5.mjs.map → renderer-BOvCstdU.mjs.map} +1 -1
  74. package/dist/renderer-BPJ79b47.mjs +5 -0
  75. package/dist/renderer-BPbM_6WR.mjs +5 -0
  76. package/dist/{renderer-DjwF10p2.mjs → renderer-BPsi7Q7Z.mjs} +3 -3
  77. package/dist/{renderer-DjwF10p2.mjs.map → renderer-BPsi7Q7Z.mjs.map} +1 -1
  78. package/dist/{renderer-BYj8x8YM.mjs → renderer-BTJyO6ov.mjs} +2 -2
  79. package/dist/{renderer-BYj8x8YM.mjs.map → renderer-BTJyO6ov.mjs.map} +1 -1
  80. package/dist/{renderer-DmOewVsB.mjs → renderer-BZCLdb8F.mjs} +3 -3
  81. package/dist/{renderer-DmOewVsB.mjs.map → renderer-BZCLdb8F.mjs.map} +1 -1
  82. package/dist/{renderer-CbNW0KHh.mjs → renderer-BZF48o1T.mjs} +3 -3
  83. package/dist/{renderer-CbNW0KHh.mjs.map → renderer-BZF48o1T.mjs.map} +1 -1
  84. package/dist/{renderer-_Fh9IBlE.mjs → renderer-BZWiY6Jw.mjs} +3 -3
  85. package/dist/{renderer-_Fh9IBlE.mjs.map → renderer-BZWiY6Jw.mjs.map} +1 -1
  86. package/dist/{renderer-Fm2brScz.mjs → renderer-BZvWlGsa.mjs} +3 -3
  87. package/dist/{renderer-Fm2brScz.mjs.map → renderer-BZvWlGsa.mjs.map} +1 -1
  88. package/dist/{renderer-DL4oR40b.mjs → renderer-BaBluD-r.mjs} +2 -2
  89. package/dist/{renderer-DL4oR40b.mjs.map → renderer-BaBluD-r.mjs.map} +1 -1
  90. package/dist/{renderer-DacnnTjY.mjs → renderer-BaHEN_Kv.mjs} +3 -3
  91. package/dist/{renderer-DacnnTjY.mjs.map → renderer-BaHEN_Kv.mjs.map} +1 -1
  92. package/dist/{renderer-Mdyv_W8K.mjs → renderer-BhZutM8K.mjs} +3 -3
  93. package/dist/{renderer-Mdyv_W8K.mjs.map → renderer-BhZutM8K.mjs.map} +1 -1
  94. package/dist/renderer-Bn-WnRh9.mjs +5 -0
  95. package/dist/renderer-BqHvDAUe.mjs +5 -0
  96. package/dist/{renderer-D8tdGi2P.mjs → renderer-BtJ6XYN7.mjs} +3 -3
  97. package/dist/{renderer-D8tdGi2P.mjs.map → renderer-BtJ6XYN7.mjs.map} +1 -1
  98. package/dist/{renderer-F410V727.mjs → renderer-BzgnTEvf.mjs} +3 -3
  99. package/dist/{renderer-F410V727.mjs.map → renderer-BzgnTEvf.mjs.map} +1 -1
  100. package/dist/{renderer-D-eTVnDS.mjs → renderer-C-La-IAt.mjs} +2 -2
  101. package/dist/{renderer-D-eTVnDS.mjs.map → renderer-C-La-IAt.mjs.map} +1 -1
  102. package/dist/{renderer-BFHgl-0f.mjs → renderer-C2ZIAhdJ.mjs} +2 -2
  103. package/dist/{renderer-BFHgl-0f.mjs.map → renderer-C2ZIAhdJ.mjs.map} +1 -1
  104. package/dist/{renderer-BebePsG7.mjs → renderer-C9pl21Zb.mjs} +2 -2
  105. package/dist/{renderer-BebePsG7.mjs.map → renderer-C9pl21Zb.mjs.map} +1 -1
  106. package/dist/renderer-CAtXDNsh.mjs +5 -0
  107. package/dist/{renderer-BkXqFmNO.mjs → renderer-CCCitH4J.mjs} +2 -2
  108. package/dist/{renderer-BkXqFmNO.mjs.map → renderer-CCCitH4J.mjs.map} +1 -1
  109. package/dist/{renderer-QMGrRiab.mjs → renderer-CCu1Q8ib.mjs} +2 -2
  110. package/dist/{renderer-QMGrRiab.mjs.map → renderer-CCu1Q8ib.mjs.map} +1 -1
  111. package/dist/{renderer-DvoPplvC.mjs → renderer-CG4TmDWH.mjs} +2 -2
  112. package/dist/{renderer-S6ez0leJ.mjs → renderer-CJmZURPQ.mjs} +2 -2
  113. package/dist/{renderer-S6ez0leJ.mjs.map → renderer-CJmZURPQ.mjs.map} +1 -1
  114. package/dist/{renderer-CPNLkir4.mjs → renderer-CN3iCli4.mjs} +2 -2
  115. package/dist/{renderer-CPNLkir4.mjs.map → renderer-CN3iCli4.mjs.map} +1 -1
  116. package/dist/{renderer--S2cNYXS.mjs → renderer-CNS8LA2_.mjs} +3 -3
  117. package/dist/{renderer--S2cNYXS.mjs.map → renderer-CNS8LA2_.mjs.map} +1 -1
  118. package/dist/renderer-C_5nfVce.mjs +5 -0
  119. package/dist/{renderer-ButaUWLr.mjs → renderer-CduDa1qf.mjs} +3 -3
  120. package/dist/{renderer-ButaUWLr.mjs.map → renderer-CduDa1qf.mjs.map} +1 -1
  121. package/dist/{renderer-Ch1rv1XP.mjs → renderer-CduL1AOK.mjs} +2 -2
  122. package/dist/{renderer-Ch1rv1XP.mjs.map → renderer-CduL1AOK.mjs.map} +1 -1
  123. package/dist/{renderer-z3YOOR3k.mjs → renderer-Cfto0m0-.mjs} +3 -3
  124. package/dist/{renderer-z3YOOR3k.mjs.map → renderer-Cfto0m0-.mjs.map} +1 -1
  125. package/dist/{renderer-CJwgsecF.mjs → renderer-CgZ1G37H.mjs} +2 -2
  126. package/dist/{renderer-CJwgsecF.mjs.map → renderer-CgZ1G37H.mjs.map} +1 -1
  127. package/dist/renderer-Cie7KKeQ.mjs +5 -0
  128. package/dist/{renderer-BaOC1Pd1.mjs → renderer-CjTxu89O.mjs} +3 -3
  129. package/dist/{renderer-BaOC1Pd1.mjs.map → renderer-CjTxu89O.mjs.map} +1 -1
  130. package/dist/{renderer-C2Ff-9u_.mjs → renderer-CoN2JgLO.mjs} +2 -2
  131. package/dist/{renderer-C2Ff-9u_.mjs.map → renderer-CoN2JgLO.mjs.map} +1 -1
  132. package/dist/{renderer-DjPC9l3w.mjs → renderer-CoO2j8AC.mjs} +3 -3
  133. package/dist/{renderer-DjPC9l3w.mjs.map → renderer-CoO2j8AC.mjs.map} +1 -1
  134. package/dist/{renderer-BRWpn3dT.mjs → renderer-CoX23zo_.mjs} +3 -3
  135. package/dist/{renderer-BRWpn3dT.mjs.map → renderer-CoX23zo_.mjs.map} +1 -1
  136. package/dist/renderer-CuoxvPsX.mjs +5 -0
  137. package/dist/renderer-Cvd3oIJQ.mjs +5 -0
  138. package/dist/renderer-CxAgpBrJ.mjs +5 -0
  139. package/dist/{renderer-C4puWDLK.mjs → renderer-CxSH3F_n.mjs} +2 -2
  140. package/dist/{renderer-C4puWDLK.mjs.map → renderer-CxSH3F_n.mjs.map} +1 -1
  141. package/dist/{renderer-B6c_Rk_2.mjs → renderer-Cz9fzS11.mjs} +3 -3
  142. package/dist/{renderer-B6c_Rk_2.mjs.map → renderer-Cz9fzS11.mjs.map} +1 -1
  143. package/dist/{renderer-BAmGO6Hj.mjs → renderer-D-IcBLT0.mjs} +3 -3
  144. package/dist/{renderer-BAmGO6Hj.mjs.map → renderer-D-IcBLT0.mjs.map} +1 -1
  145. package/dist/renderer-D-cWBX0r.mjs +5 -0
  146. package/dist/{renderer-DqlvbOUt.mjs → renderer-D-e3vDnO.mjs} +3 -3
  147. package/dist/{renderer-DqlvbOUt.mjs.map → renderer-D-e3vDnO.mjs.map} +1 -1
  148. package/dist/renderer-D0q2VyJR.mjs +5 -0
  149. package/dist/{renderer-BqSiWRoD.mjs → renderer-D4E8Yf61.mjs} +2 -2
  150. package/dist/{renderer-BqSiWRoD.mjs.map → renderer-D4E8Yf61.mjs.map} +1 -1
  151. package/dist/renderer-D6NXn3Ky.mjs +5 -0
  152. package/dist/renderer-D9bp2jor.mjs +5 -0
  153. package/dist/{renderer-GlyeFl5R.mjs → renderer-DA1brg35.mjs} +3 -3
  154. package/dist/{renderer-GlyeFl5R.mjs.map → renderer-DA1brg35.mjs.map} +1 -1
  155. package/dist/{renderer-DNhOjz6C.mjs → renderer-DAFi5TC_.mjs} +2 -2
  156. package/dist/{renderer-DNhOjz6C.mjs.map → renderer-DAFi5TC_.mjs.map} +1 -1
  157. package/dist/renderer-DEzIOIB7.mjs +5 -0
  158. package/dist/{renderer-B5Icb0XW.mjs → renderer-DFX2V9Dp.mjs} +2 -2
  159. package/dist/{renderer-B5Icb0XW.mjs.map → renderer-DFX2V9Dp.mjs.map} +1 -1
  160. package/dist/{renderer-w8LKBg_5.mjs → renderer-DH0wkQkR.mjs} +2 -2
  161. package/dist/{renderer-w8LKBg_5.mjs.map → renderer-DH0wkQkR.mjs.map} +1 -1
  162. package/dist/{renderer-BXCZev4l.mjs → renderer-DOZJeWM4.mjs} +3 -3
  163. package/dist/{renderer-BXCZev4l.mjs.map → renderer-DOZJeWM4.mjs.map} +1 -1
  164. package/dist/{renderer-Cz11KJmQ.mjs → renderer-DOkEalxy.mjs} +2 -2
  165. package/dist/{renderer-Cz11KJmQ.mjs.map → renderer-DOkEalxy.mjs.map} +1 -1
  166. package/dist/renderer-DSSTVulw.mjs +5 -0
  167. package/dist/renderer-DUmTURPu.mjs +5 -0
  168. package/dist/{renderer-BI1nmIqA.mjs → renderer-DVaVVz-D.mjs} +3 -3
  169. package/dist/{renderer-BI1nmIqA.mjs.map → renderer-DVaVVz-D.mjs.map} +1 -1
  170. package/dist/renderer-DXCAgI9m.mjs +5 -0
  171. package/dist/{renderer-BlafvK-L.mjs → renderer-DY8fPeH3.mjs} +2 -2
  172. package/dist/renderer-DZ9j3fQj.mjs +5 -0
  173. package/dist/{renderer-6QAlE1Mp.mjs → renderer-DZndEvZN.mjs} +3 -3
  174. package/dist/{renderer-6QAlE1Mp.mjs.map → renderer-DZndEvZN.mjs.map} +1 -1
  175. package/dist/{renderer-B3x-uogM.mjs → renderer-DaV95CAd.mjs} +3 -3
  176. package/dist/{renderer-B3x-uogM.mjs.map → renderer-DaV95CAd.mjs.map} +1 -1
  177. package/dist/{renderer-YtRSM2cv.mjs → renderer-DautVsW4.mjs} +2 -2
  178. package/dist/{renderer-YtRSM2cv.mjs.map → renderer-DautVsW4.mjs.map} +1 -1
  179. package/dist/renderer-Dd1G_VKM.mjs +5 -0
  180. package/dist/renderer-Dee8079X.mjs +5 -0
  181. package/dist/{renderer-B48vtpou.mjs → renderer-Dg5yLAM9.mjs} +3 -3
  182. package/dist/{renderer-B48vtpou.mjs.map → renderer-Dg5yLAM9.mjs.map} +1 -1
  183. package/dist/renderer-DgWh5noB.mjs +5 -0
  184. package/dist/{renderer-DlESyfg7.d.mts → renderer-Dim2ZlfK.d.mts} +2 -2
  185. package/dist/{renderer-DlESyfg7.d.mts.map → renderer-Dim2ZlfK.d.mts.map} +1 -1
  186. package/dist/{renderer-R0sJId_6.mjs → renderer-Dk0dnuHr.mjs} +2 -2
  187. package/dist/{renderer-R0sJId_6.mjs.map → renderer-Dk0dnuHr.mjs.map} +1 -1
  188. package/dist/{renderer-Bf4BdBtb.mjs → renderer-DkM_wN8j.mjs} +2 -2
  189. package/dist/{renderer-Bf4BdBtb.mjs.map → renderer-DkM_wN8j.mjs.map} +1 -1
  190. package/dist/{renderer-DS64UaYK.mjs → renderer-DkYiOWPJ.mjs} +3 -3
  191. package/dist/{renderer-DS64UaYK.mjs.map → renderer-DkYiOWPJ.mjs.map} +1 -1
  192. package/dist/{renderer-ITtBRR4o.mjs → renderer-DqgduGiO.mjs} +3 -3
  193. package/dist/{renderer-ITtBRR4o.mjs.map → renderer-DqgduGiO.mjs.map} +1 -1
  194. package/dist/renderer-DuD9QRix.mjs +5 -0
  195. package/dist/{renderer--Da6pAJ4.mjs → renderer-Edn9xltT.mjs} +2 -2
  196. package/dist/renderer-KtG5p44R.mjs +5 -0
  197. package/dist/{renderer-DDBmBmQN.mjs → renderer-Tf1AS1aO.mjs} +2 -2
  198. package/dist/{renderer-DDBmBmQN.mjs.map → renderer-Tf1AS1aO.mjs.map} +1 -1
  199. package/dist/{renderer-_-MCpvGq.mjs → renderer-TyZu7nQ2.mjs} +69 -69
  200. package/dist/{renderer-_-MCpvGq.mjs.map → renderer-TyZu7nQ2.mjs.map} +1 -1
  201. package/dist/{renderer-CgEMtLOL.mjs → renderer-eWBBsCv2.mjs} +2 -2
  202. package/dist/{renderer-CgEMtLOL.mjs.map → renderer-eWBBsCv2.mjs.map} +1 -1
  203. package/dist/{renderer-ClK7_Yzz.mjs → renderer-fXRwAdO2.mjs} +2 -2
  204. package/dist/{renderer-ClK7_Yzz.mjs.map → renderer-fXRwAdO2.mjs.map} +1 -1
  205. package/dist/renderer-fdVdNIhL.mjs +5 -0
  206. package/dist/renderer-gBKWL0_E.mjs +5 -0
  207. package/dist/{renderer-D90PpIKG.mjs → renderer-gbBgGPNL.mjs} +2 -2
  208. package/dist/{renderer-Kj9AhRCc.mjs → renderer-h9sJnoxw.mjs} +3 -3
  209. package/dist/{renderer-Kj9AhRCc.mjs.map → renderer-h9sJnoxw.mjs.map} +1 -1
  210. package/dist/renderer-hmWhsR6C.mjs +5 -0
  211. package/dist/{renderer-Rg1BFv8B.mjs → renderer-i4RJ-_sx.mjs} +2 -2
  212. package/dist/{renderer-Rg1BFv8B.mjs.map → renderer-i4RJ-_sx.mjs.map} +1 -1
  213. package/dist/renderer-jkL9FU2W.mjs +6 -0
  214. package/dist/{renderer-8k-TR_Cy.mjs → renderer-nMnE_T2y.mjs} +3 -3
  215. package/dist/{renderer-8k-TR_Cy.mjs.map → renderer-nMnE_T2y.mjs.map} +1 -1
  216. package/dist/{renderer-CczmOPAO.mjs → renderer-q971j1BW.mjs} +3 -3
  217. package/dist/{renderer-CczmOPAO.mjs.map → renderer-q971j1BW.mjs.map} +1 -1
  218. package/dist/{renderer-DJWQWN1E.mjs → renderer-wFyrVuuj.mjs} +3 -3
  219. package/dist/{renderer-DJWQWN1E.mjs.map → renderer-wFyrVuuj.mjs.map} +1 -1
  220. package/dist/renderer-yAWv-daq.mjs +5 -0
  221. package/dist/{renderer-DYo_a5-8.mjs → renderer-yGARXwV0.mjs} +2 -2
  222. package/dist/{renderer-DYo_a5-8.mjs.map → renderer-yGARXwV0.mjs.map} +1 -1
  223. package/dist/{renderers-CP8CFWDs-DXiR_PLR.mjs → renderers-CP8CFWDs-DoVn7dbW.mjs} +2 -2
  224. package/dist/{renderers-CP8CFWDs-DXiR_PLR.mjs.map → renderers-CP8CFWDs-DoVn7dbW.mjs.map} +1 -1
  225. package/dist/signals/index.d.mts +5 -5
  226. package/dist/signals/index.mjs +4 -4
  227. package/package.json +57 -56
  228. package/dist/dist-CturBK2Z.mjs.map +0 -1
  229. package/dist/index-AdrZIWT0.d.mts.map +0 -1
  230. package/dist/preview-engine/chunk-U7NIY7UC.mjs +0 -1522
  231. package/dist/renderer-1hypa-l7.mjs +0 -5
  232. package/dist/renderer-6D8M4DRu.mjs +0 -5
  233. package/dist/renderer-APrj6tdP.mjs +0 -5
  234. package/dist/renderer-BF8wlbN1.mjs +0 -5
  235. package/dist/renderer-BOUcMCJB.mjs +0 -5
  236. package/dist/renderer-BSwn6KmG.mjs +0 -5
  237. package/dist/renderer-BUxvuC5A.mjs +0 -5
  238. package/dist/renderer-BYOsmJ8F.mjs +0 -5
  239. package/dist/renderer-Bp7LqSTt.mjs +0 -5
  240. package/dist/renderer-CD0amH_Q.mjs +0 -5
  241. package/dist/renderer-CIJGjrt7.mjs +0 -5
  242. package/dist/renderer-CNFTl94b.mjs +0 -5
  243. package/dist/renderer-CTJ0gHPS.mjs +0 -5
  244. package/dist/renderer-Cc3INn7w.mjs +0 -5
  245. package/dist/renderer-CeHh2lKY.mjs +0 -5
  246. package/dist/renderer-CfjwkQP4.mjs +0 -5
  247. package/dist/renderer-CiuOB-Md.mjs +0 -5
  248. package/dist/renderer-Cor66Y2X.mjs +0 -5
  249. package/dist/renderer-CtIM0LWe.mjs +0 -5
  250. package/dist/renderer-CyqgTsnx.mjs +0 -5
  251. package/dist/renderer-D2QG9yIW.mjs +0 -5
  252. package/dist/renderer-D2woU27o.mjs +0 -6
  253. package/dist/renderer-DAhDiQQY.mjs +0 -5
  254. package/dist/renderer-DIjxOQFi.mjs +0 -5
  255. package/dist/renderer-DRzalGhU.mjs +0 -5
  256. package/dist/renderer-DY6S1jWp.mjs +0 -5
  257. package/dist/renderer-DeW0KNQV.mjs +0 -5
  258. package/dist/renderer-DsVSVFkl.mjs +0 -5
  259. package/dist/renderer-DxvcIgEU.mjs +0 -5
  260. package/dist/renderer-F0MKqmol.mjs +0 -5
  261. package/dist/renderer-OkbovcAc.mjs +0 -5
  262. package/dist/renderer-QyBeWMEL.mjs +0 -5
  263. package/dist/renderer-hSh5IrDp.mjs +0 -5
  264. package/dist/renderer-puLncHft.mjs +0 -5
  265. package/dist/renderer-wnFCaw7j.mjs +0 -5
  266. package/dist/renderer-xjtlyGS1.mjs +0 -5
@@ -12675,6 +12675,63 @@ var InputStore = class {
12675
12675
  }
12676
12676
  };
12677
12677
  const inputStore = new InputStore();
12678
+ /** The texture formats the renderer decodes (sharp); others (TGA, BMP, PSD, DDS, …) are skipped. */
12679
+ const MIME_BY_EXT = {
12680
+ ".png": "image/png",
12681
+ ".jpg": "image/jpeg",
12682
+ ".jpeg": "image/jpeg",
12683
+ ".webp": "image/webp",
12684
+ ".gif": "image/gif",
12685
+ ".tif": "image/tiff",
12686
+ ".tiff": "image/tiff",
12687
+ ".avif": "image/avif"
12688
+ };
12689
+ /** MIME type of an image file name, by extension (PNG when unknown). */
12690
+ function textureMime(name) {
12691
+ return MIME_BY_EXT[path.extname(name).toLowerCase()] ?? "image/png";
12692
+ }
12693
+ /** Whether the renderer can decode a texture with this file name (no extension: assume yes). */
12694
+ function isSupportedTexture(name) {
12695
+ const ext = path.extname(name.replace(/\\/g, "/")).toLowerCase();
12696
+ return !ext || ext in MIME_BY_EXT;
12697
+ }
12698
+ /**
12699
+ * Reads a texture a model file refers to. Exporters often write absolute
12700
+ * paths from the author's machine (`C:\Users\…\wood.png`), so after the
12701
+ * path as written (relative to `basePath`), the file name is also tried in
12702
+ * `basePath`, its `textures/` folder and any `*.fbm` folder next to it.
12703
+ */
12704
+ function readTextureFile(ref, basePath) {
12705
+ if (!ref || typeof fs === "undefined" || !fs.existsSync) return void 0;
12706
+ if (!isSupportedTexture(ref)) return void 0;
12707
+ let cleaned = ref.trim().replace(/^file:\/\//, "");
12708
+ try {
12709
+ cleaned = decodeURIComponent(cleaned);
12710
+ } catch (_) {}
12711
+ const normalized = cleaned.replace(/\\/g, "/");
12712
+ const fileName = normalized.split("/").pop() ?? normalized;
12713
+ const candidates = [];
12714
+ if (path.isAbsolute(normalized)) candidates.push(normalized);
12715
+ if (basePath) {
12716
+ candidates.push(path.resolve(basePath, normalized));
12717
+ candidates.push(path.join(basePath, fileName));
12718
+ candidates.push(path.join(basePath, "textures", fileName));
12719
+ try {
12720
+ for (const entry of fs.readdirSync(basePath)) if (entry.toLowerCase().endsWith(".fbm")) candidates.push(path.join(basePath, entry, fileName));
12721
+ } catch (_) {}
12722
+ }
12723
+ for (const file of candidates) try {
12724
+ if (fs.existsSync(file) && fs.statSync(file).isFile()) return {
12725
+ bytes: new Uint8Array(fs.readFileSync(file)),
12726
+ mime: textureMime(file)
12727
+ };
12728
+ } catch (_) {}
12729
+ }
12730
+ /** Warns that a model's texture is missing or in an unsupported format (the mesh renders untextured). */
12731
+ function warnMissingTexture(loader, material, ref) {
12732
+ const reason = isSupportedTexture(ref) ? "was not found" : `is in an unsupported format (use ${Object.keys(MIME_BY_EXT).join(", ")})`;
12733
+ console.warn(`[${loader}] Texture "${ref}" for material "${material}" ${reason}; the material renders without it.`);
12734
+ }
12678
12735
  var ObjLoader = class ObjLoader$1 {
12679
12736
  /**
12680
12737
  * Parses Wavefront .obj text content (and optional .mtl text) into Model3DData.
@@ -12692,7 +12749,7 @@ var ObjLoader = class ObjLoader$1 {
12692
12749
  };
12693
12750
  meshBuilders.push(currentBuilder);
12694
12751
  const materials = {};
12695
- if (mtlText) Object.assign(materials, ObjLoader$1.parseMtl(mtlText));
12752
+ if (mtlText) Object.assign(materials, ObjLoader$1.loadTextures(ObjLoader$1.parseMtl(mtlText), options.basePath));
12696
12753
  const lines = objText.split(/\r?\n/);
12697
12754
  for (let i = 0; i < lines.length; i++) {
12698
12755
  const line = lines[i]?.trim();
@@ -12705,7 +12762,7 @@ var ObjLoader = class ObjLoader$1 {
12705
12762
  const resolvedMtlPath = path.resolve(options.basePath, mtlFileName);
12706
12763
  if (fs.existsSync(resolvedMtlPath)) {
12707
12764
  const content = fs.readFileSync(resolvedMtlPath, "utf-8");
12708
- Object.assign(materials, ObjLoader$1.parseMtl(content));
12765
+ Object.assign(materials, ObjLoader$1.loadTextures(ObjLoader$1.parseMtl(content), path.dirname(resolvedMtlPath)));
12709
12766
  }
12710
12767
  } catch (_) {}
12711
12768
  } else if (cmd === "v") {
@@ -12793,12 +12850,12 @@ var ObjLoader = class ObjLoader$1 {
12793
12850
  maxY = 1;
12794
12851
  maxZ = 1;
12795
12852
  }
12796
- let centerX = (minX + maxX) * .5;
12797
- let centerY = (minY + maxY) * .5;
12798
- let centerZ = (minZ + maxZ) * .5;
12799
- let sizeX = Math.max(1e-4, maxX - minX);
12800
- let sizeY = Math.max(1e-4, maxY - minY);
12801
- let sizeZ = Math.max(1e-4, maxZ - minZ);
12853
+ const centerX = (minX + maxX) * .5;
12854
+ const centerY = (minY + maxY) * .5;
12855
+ const centerZ = (minZ + maxZ) * .5;
12856
+ const sizeX = Math.max(1e-4, maxX - minX);
12857
+ const sizeY = Math.max(1e-4, maxY - minY);
12858
+ const sizeZ = Math.max(1e-4, maxZ - minZ);
12802
12859
  const maxDim = Math.max(sizeX, sizeY, sizeZ);
12803
12860
  let scaleMultiplier = 1;
12804
12861
  if (options.normalizeSize && options.normalizeSize > 0) scaleMultiplier = options.normalizeSize / maxDim;
@@ -13012,8 +13069,57 @@ var ObjLoader = class ObjLoader$1 {
13012
13069
  const val = Number.parseFloat(parts[1] ?? "1");
13013
13070
  currentMat.opacity = cmd === "d" ? val : 1 - val;
13014
13071
  if (currentMat.diffuseColor) currentMat.diffuseColor[3] = currentMat.opacity;
13015
- } else if (cmd === "map_kd") currentMat.diffuseTexture = parts.slice(1).join(" ");
13016
- else if (cmd === "map_bump" || cmd === "bump") currentMat.normalTexture = parts.slice(1).join(" ");
13072
+ } else if (cmd === "map_kd") {
13073
+ currentMat.diffuseTexture = ObjLoader$1.mapFileName(parts);
13074
+ const clamp = parts.findIndex((p) => p.toLowerCase() === "-clamp");
13075
+ currentMat.textureWrap = clamp > 0 && parts[clamp + 1]?.toLowerCase() === "on" ? "clamp" : "repeat";
13076
+ } else if (cmd === "norm" || cmd === "map_kn") currentMat.normalTexture = ObjLoader$1.mapFileName(parts);
13077
+ }
13078
+ }
13079
+ return materials;
13080
+ }
13081
+ /** Option arities of MTL map statements (`-o`, `-s`, `-t` take 1–3 numbers). */
13082
+ static MAP_OPTION_ARGS = {
13083
+ "-blendu": 1,
13084
+ "-blendv": 1,
13085
+ "-cc": 1,
13086
+ "-clamp": 1,
13087
+ "-texres": 1,
13088
+ "-bm": 1,
13089
+ "-boost": 1,
13090
+ "-imfchan": 1,
13091
+ "-type": 1,
13092
+ "-mm": 2
13093
+ };
13094
+ /** The file name of a map statement (`map_Kd -s 1 1 1 wood grain.png`), past its options. */
13095
+ static mapFileName(parts) {
13096
+ let i = 1;
13097
+ while (i < parts.length && parts[i]?.startsWith("-")) {
13098
+ const option = parts[i].toLowerCase();
13099
+ i++;
13100
+ if (option === "-o" || option === "-s" || option === "-t") for (let n = 0; n < 3 && i < parts.length; n++) {
13101
+ if (Number.isNaN(Number(parts[i]))) break;
13102
+ i++;
13103
+ }
13104
+ else i += ObjLoader$1.MAP_OPTION_ARGS[option] ?? 0;
13105
+ }
13106
+ return parts.slice(i).join(" ");
13107
+ }
13108
+ /** Reads the diffuse and normal maps the materials name, relative to `dir`. */
13109
+ static loadTextures(materials, dir) {
13110
+ if (!dir) return materials;
13111
+ for (const mat of Object.values(materials)) {
13112
+ if (mat.diffuseTexture && !mat.diffuseTextureBuffer) {
13113
+ const file = readTextureFile(mat.diffuseTexture, dir);
13114
+ if (file) {
13115
+ mat.diffuseTextureBuffer = file.bytes;
13116
+ mat.diffuseTextureMime = file.mime;
13117
+ } else warnMissingTexture("ObjLoader", mat.name, mat.diffuseTexture);
13118
+ }
13119
+ if (mat.normalTexture && !mat.normalTextureBuffer) {
13120
+ const file = readTextureFile(mat.normalTexture, dir);
13121
+ if (file) mat.normalTextureBuffer = file.bytes;
13122
+ else warnMissingTexture("ObjLoader", mat.name, mat.normalTexture);
13017
13123
  }
13018
13124
  }
13019
13125
  return materials;
@@ -13049,192 +13155,501 @@ function loadOBJ(srcOrData, mtlText, options = {}) {
13049
13155
  }
13050
13156
  return ObjLoader.parse(text, mtlText, options);
13051
13157
  }
13052
- var FbxLoader = class FbxLoader$1 {
13053
- static FBX_KTIME = 46186158e3;
13054
- /**
13055
- * Parses binary or text FBX buffer/string into Model3DData.
13056
- */
13057
- static parse(bufferOrString, options = {}) {
13058
- const rootNode = typeof bufferOrString === "string" ? FbxLoader$1.parseAscii(bufferOrString) : FbxLoader$1.isBinary(bufferOrString) ? FbxLoader$1.parseBinary(bufferOrString) : FbxLoader$1.parseAscii(new TextDecoder().decode(bufferOrString));
13059
- return FbxLoader$1.buildModelFromTree(rootNode, options);
13158
+ /**
13159
+ * Adds each triangle's (area-weighted) face normal to its three vertices, then
13160
+ * normalizes every vertex normal in place.
13161
+ */
13162
+ function accumulateVertexNormals(positions, indices, normals) {
13163
+ const vertex = (i) => [
13164
+ positions[i * 3],
13165
+ positions[i * 3 + 1],
13166
+ positions[i * 3 + 2]
13167
+ ];
13168
+ for (let i = 0; i < indices.length; i += 3) {
13169
+ const i0 = indices[i];
13170
+ const i1 = indices[i + 1];
13171
+ const i2 = indices[i + 2];
13172
+ const p0 = vertex(i0);
13173
+ const fn = Vector3Math.cross(Vector3Math.subtract(vertex(i1), p0), Vector3Math.subtract(vertex(i2), p0));
13174
+ for (const v of [
13175
+ i0,
13176
+ i1,
13177
+ i2
13178
+ ]) {
13179
+ normals[v * 3] += fn[0];
13180
+ normals[v * 3 + 1] += fn[1];
13181
+ normals[v * 3 + 2] += fn[2];
13182
+ }
13060
13183
  }
13061
- static isBinary(buffer) {
13062
- const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
13063
- if (view.length < 27) return false;
13064
- const header = "Kaydara FBX Binary \0\0";
13065
- for (let i = 0; i < 23; i++) if (view[i] !== header.charCodeAt(i)) return false;
13066
- return true;
13184
+ for (let i = 0; i < normals.length; i += 3) {
13185
+ const nx = normals[i];
13186
+ const ny = normals[i + 1];
13187
+ const nz = normals[i + 2];
13188
+ const len = Math.hypot(nx, ny, nz) || 1;
13189
+ normals[i] = nx / len;
13190
+ normals[i + 1] = ny / len;
13191
+ normals[i + 2] = nz / len;
13067
13192
  }
13068
- /**
13069
- * Parses Binary FBX 7.x/6.x format.
13070
- */
13071
- static parseBinary(buffer) {
13072
- const u8 = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
13073
- const view = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
13074
- const is64Bit = view.getUint32(23, true) >= 7500;
13075
- let offset = 27;
13076
- const root = {
13077
- name: "Root",
13078
- props: [],
13079
- children: []
13080
- };
13081
- const readNode = () => {
13082
- if (offset >= u8.byteLength) return null;
13083
- let endOffset;
13084
- let numProperties;
13085
- let nameLen;
13086
- if (is64Bit) {
13087
- if (offset + 25 > u8.byteLength) return null;
13088
- endOffset = Number(view.getBigUint64(offset, true));
13089
- numProperties = Number(view.getBigUint64(offset + 8, true));
13090
- nameLen = view.getUint8(offset + 24);
13091
- offset += 25;
13092
- } else {
13093
- if (offset + 13 > u8.byteLength) return null;
13094
- endOffset = view.getUint32(offset, true);
13095
- numProperties = view.getUint32(offset + 4, true);
13096
- nameLen = view.getUint8(offset + 12);
13097
- offset += 13;
13098
- }
13099
- if (endOffset === 0) return null;
13100
- const name = new TextDecoder().decode(u8.subarray(offset, offset + nameLen));
13101
- offset += nameLen;
13102
- const props = [];
13103
- for (let p = 0; p < numProperties; p++) {
13104
- const typeCode = String.fromCharCode(u8[offset]);
13105
- offset += 1;
13106
- switch (typeCode) {
13107
- case "Y":
13108
- props.push(view.getInt16(offset, true));
13109
- offset += 2;
13110
- break;
13111
- case "C":
13112
- case "B":
13113
- props.push(view.getUint8(offset) !== 0);
13114
- offset += 1;
13115
- break;
13116
- case "I":
13117
- props.push(view.getInt32(offset, true));
13118
- offset += 4;
13119
- break;
13120
- case "F":
13121
- props.push(view.getFloat32(offset, true));
13122
- offset += 4;
13123
- break;
13124
- case "D":
13125
- props.push(view.getFloat64(offset, true));
13126
- offset += 8;
13127
- break;
13128
- case "L":
13129
- props.push(Number(view.getBigInt64(offset, true)));
13130
- offset += 8;
13131
- break;
13132
- case "S": {
13133
- const len = view.getUint32(offset, true);
13134
- offset += 4;
13135
- const str = new TextDecoder().decode(u8.subarray(offset, offset + len));
13136
- props.push(str);
13137
- offset += len;
13138
- break;
13139
- }
13140
- case "R": {
13141
- const len = view.getUint32(offset, true);
13142
- offset += 4;
13143
- const raw = u8.subarray(offset, offset + len);
13144
- props.push(raw);
13145
- offset += len;
13146
- break;
13147
- }
13148
- case "f":
13149
- case "d":
13150
- case "i":
13151
- case "l":
13152
- case "b":
13153
- case "c": {
13154
- const arrayLen = view.getUint32(offset, true);
13155
- const encoding = view.getUint32(offset + 4, true);
13156
- const compressedLen = view.getUint32(offset + 8, true);
13157
- offset += 12;
13158
- let arrayBuffer;
13159
- if (encoding === 1) {
13160
- arrayBuffer = inflateSync(u8.subarray(offset, offset + compressedLen));
13161
- offset += compressedLen;
13162
- } else {
13163
- const totalBytes = arrayLen * (typeCode === "d" || typeCode === "l" ? 8 : typeCode === "f" || typeCode === "i" ? 4 : 1);
13164
- arrayBuffer = u8.subarray(offset, offset + totalBytes);
13165
- offset += totalBytes;
13166
- }
13167
- const arrayView = new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
13168
- if (typeCode === "f") {
13169
- const arr = new Float32Array(arrayLen);
13170
- for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getFloat32(k * 4, true);
13171
- props.push(arr);
13172
- } else if (typeCode === "d") {
13173
- const arr = new Float64Array(arrayLen);
13174
- for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getFloat64(k * 8, true);
13175
- props.push(arr);
13176
- } else if (typeCode === "i") {
13177
- const arr = new Int32Array(arrayLen);
13178
- for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getInt32(k * 4, true);
13179
- props.push(arr);
13180
- } else if (typeCode === "l") {
13181
- const arr = new Float64Array(arrayLen);
13182
- for (let k = 0; k < arrayLen; k++) arr[k] = Number(arrayView.getBigInt64(k * 8, true));
13183
- props.push(arr);
13184
- } else if (typeCode === "b") {
13185
- const arr = new Uint8Array(arrayLen);
13186
- for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getUint8(k);
13187
- props.push(arr);
13188
- } else props.push(arrayBuffer);
13189
- break;
13190
- }
13191
- default: break;
13192
- }
13193
- }
13194
- const children = [];
13195
- while (offset < endOffset) {
13196
- const child = readNode();
13197
- if (child) children.push(child);
13198
- else break;
13199
- }
13200
- offset = endOffset;
13201
- return {
13202
- name,
13203
- props,
13204
- children
13205
- };
13206
- };
13207
- while (offset < u8.byteLength - (is64Bit ? 25 : 13)) {
13208
- const node = readNode();
13209
- if (node) root.children.push(node);
13210
- else break;
13211
- }
13212
- return root;
13193
+ }
13194
+ /** Axis-aligned bounds of xyz position arrays (all zero when empty). */
13195
+ function computeBounds(...positionArrays) {
13196
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
13197
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
13198
+ for (const positions of positionArrays) for (let i = 0; i < positions.length; i += 3) {
13199
+ const x = positions[i];
13200
+ const y = positions[i + 1];
13201
+ const z = positions[i + 2];
13202
+ if (x < minX) minX = x;
13203
+ if (x > maxX) maxX = x;
13204
+ if (y < minY) minY = y;
13205
+ if (y > maxY) maxY = y;
13206
+ if (z < minZ) minZ = z;
13207
+ if (z > maxZ) maxZ = z;
13213
13208
  }
13214
- /**
13215
- * Parses ASCII FBX format.
13216
- */
13217
- static parseAscii(text) {
13218
- const root = {
13219
- name: "Root",
13220
- props: [],
13221
- children: []
13222
- };
13223
- const stack = [root];
13224
- const lines = text.split(/\r?\n/);
13225
- for (let i = 0; i < lines.length; i++) {
13226
- const line = lines[i]?.trim();
13227
- if (!line || line.startsWith(";")) continue;
13228
- if (line === "}") {
13229
- if (stack.length > 1) stack.pop();
13230
- continue;
13231
- }
13232
- const colonIdx = line.indexOf(":");
13233
- if (colonIdx === -1) continue;
13234
- const name = line.substring(0, colonIdx).trim();
13235
- const rest = line.substring(colonIdx + 1).trim();
13236
- const hasBrace = rest.endsWith("{");
13237
- const propStr = hasBrace ? rest.slice(0, -1).trim() : rest;
13209
+ if (!Number.isFinite(minX)) {
13210
+ minX = minY = minZ = 0;
13211
+ maxX = maxY = maxZ = 0;
13212
+ }
13213
+ return {
13214
+ min: [
13215
+ minX,
13216
+ minY,
13217
+ minZ
13218
+ ],
13219
+ max: [
13220
+ maxX,
13221
+ maxY,
13222
+ maxZ
13223
+ ],
13224
+ center: [
13225
+ (minX + maxX) * .5,
13226
+ (minY + maxY) * .5,
13227
+ (minZ + maxZ) * .5
13228
+ ],
13229
+ size: [
13230
+ maxX - minX,
13231
+ maxY - minY,
13232
+ maxZ - minZ
13233
+ ]
13234
+ };
13235
+ }
13236
+ /**
13237
+ * The placement `LoadModelOptions` asks for: recentring on the bounds
13238
+ * (`center`), fitting the largest side to `normalizeSize`, then `scale`.
13239
+ */
13240
+ function resolveModelTransform(bounds, options) {
13241
+ const maxDimension = Math.max(...bounds.size, 1e-4);
13242
+ let scaleMultiplier = 1;
13243
+ if (options.normalizeSize && options.normalizeSize > 0) scaleMultiplier = options.normalizeSize / maxDimension;
13244
+ const scale = [
13245
+ scaleMultiplier,
13246
+ scaleMultiplier,
13247
+ scaleMultiplier
13248
+ ];
13249
+ if (typeof options.scale === "number") {
13250
+ scale[0] *= options.scale;
13251
+ scale[1] *= options.scale;
13252
+ scale[2] *= options.scale;
13253
+ } else if (Array.isArray(options.scale)) {
13254
+ scale[0] *= options.scale[0];
13255
+ scale[1] *= options.scale[1];
13256
+ scale[2] *= options.scale[2];
13257
+ }
13258
+ return {
13259
+ offset: options.center ? bounds.center : [
13260
+ 0,
13261
+ 0,
13262
+ 0
13263
+ ],
13264
+ scale
13265
+ };
13266
+ }
13267
+ /** `(p - offset) * scale` for every xyz position. */
13268
+ function transformPositions(positions, { offset, scale }) {
13269
+ const out = new Float32Array(positions.length);
13270
+ for (let i = 0; i < positions.length; i += 3) {
13271
+ out[i] = (positions[i] - offset[0]) * scale[0];
13272
+ out[i + 1] = (positions[i + 1] - offset[1]) * scale[1];
13273
+ out[i + 2] = (positions[i + 2] - offset[2]) * scale[2];
13274
+ }
13275
+ return out;
13276
+ }
13277
+ /** Uint16 indices while every vertex fits, Uint32 beyond 65535 vertices. */
13278
+ function createIndexArray(indices, vertexCount) {
13279
+ return vertexCount > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
13280
+ }
13281
+ /** The model's bounds after `transform`, as `Model3DData.bounds` reports them. */
13282
+ function transformModelBounds(bounds, { offset, scale }) {
13283
+ const min = [
13284
+ (bounds.min[0] - offset[0]) * scale[0],
13285
+ (bounds.min[1] - offset[1]) * scale[1],
13286
+ (bounds.min[2] - offset[2]) * scale[2]
13287
+ ];
13288
+ const max = [
13289
+ (bounds.max[0] - offset[0]) * scale[0],
13290
+ (bounds.max[1] - offset[1]) * scale[1],
13291
+ (bounds.max[2] - offset[2]) * scale[2]
13292
+ ];
13293
+ return {
13294
+ min,
13295
+ max,
13296
+ center: [
13297
+ (min[0] + max[0]) * .5,
13298
+ (min[1] + max[1]) * .5,
13299
+ (min[2] + max[2]) * .5
13300
+ ],
13301
+ size: [
13302
+ max[0] - min[0],
13303
+ max[1] - min[1],
13304
+ max[2] - min[2]
13305
+ ],
13306
+ boundingSphereRadius: Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) * .5
13307
+ };
13308
+ }
13309
+ /**
13310
+ * Poses a model's scene graph (glTF semantics): samples an animation clip's
13311
+ * translation / rotation / scale tracks at a time, composes the node
13312
+ * hierarchy into model-space matrices, and turns a skin's joints into the
13313
+ * joint matrices the mesh shader blends (joint world × inverse bind).
13314
+ */
13315
+ /** Joints per skin the mesh shader holds (mesh3d.wgsl jointMatrices). */
13316
+ const MAX_JOINTS = 128;
13317
+ /** Column-major translate · rotate(quaternion) · scale. */
13318
+ function trsMatrix(t, q, s) {
13319
+ const [x, y, z, w] = q;
13320
+ const xx = x * x;
13321
+ const yy = y * y;
13322
+ const zz = z * z;
13323
+ const xy = x * y;
13324
+ const xz = x * z;
13325
+ const yz = y * z;
13326
+ const wx = w * x;
13327
+ const wy = w * y;
13328
+ const wz = w * z;
13329
+ return [
13330
+ (1 - 2 * (yy + zz)) * s[0],
13331
+ 2 * (xy + wz) * s[0],
13332
+ 2 * (xz - wy) * s[0],
13333
+ 0,
13334
+ 2 * (xy - wz) * s[1],
13335
+ (1 - 2 * (xx + zz)) * s[1],
13336
+ 2 * (yz + wx) * s[1],
13337
+ 0,
13338
+ 2 * (xz + wy) * s[2],
13339
+ 2 * (yz - wx) * s[2],
13340
+ (1 - 2 * (xx + yy)) * s[2],
13341
+ 0,
13342
+ t[0],
13343
+ t[1],
13344
+ t[2],
13345
+ 1
13346
+ ];
13347
+ }
13348
+ function normalizeQuat(q) {
13349
+ const len = Math.hypot(q[0], q[1], q[2], q[3]) || 1;
13350
+ return [
13351
+ q[0] / len,
13352
+ q[1] / len,
13353
+ q[2] / len,
13354
+ q[3] / len
13355
+ ];
13356
+ }
13357
+ function slerp(a, b, t) {
13358
+ let cos = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
13359
+ const sign = cos < 0 ? -1 : 1;
13360
+ cos *= sign;
13361
+ if (cos > .9995) return normalizeQuat([
13362
+ a[0] + (sign * b[0] - a[0]) * t,
13363
+ a[1] + (sign * b[1] - a[1]) * t,
13364
+ a[2] + (sign * b[2] - a[2]) * t,
13365
+ a[3] + (sign * b[3] - a[3]) * t
13366
+ ]);
13367
+ const theta = Math.acos(cos);
13368
+ const sin = Math.sin(theta);
13369
+ const wa = Math.sin((1 - t) * theta) / sin;
13370
+ const wb = sign * Math.sin(t * theta) / sin;
13371
+ return [
13372
+ a[0] * wa + b[0] * wb,
13373
+ a[1] * wa + b[1] * wb,
13374
+ a[2] * wa + b[2] * wb,
13375
+ a[3] * wa + b[3] * wb
13376
+ ];
13377
+ }
13378
+ /** Index of the last keyframe at or before `time` (binary search). */
13379
+ function keyBefore(times, time$2) {
13380
+ let lo = 0;
13381
+ let hi = times.length - 1;
13382
+ while (lo < hi) {
13383
+ const mid = lo + hi + 1 >> 1;
13384
+ if ((times[mid] ?? 0) <= time$2) lo = mid;
13385
+ else hi = mid - 1;
13386
+ }
13387
+ return lo;
13388
+ }
13389
+ /** Element `k` (of `size` components) of a track's values; cubic tracks store [in, value, out] triples. */
13390
+ function valueAt(values, k, size, cubic, part = 1) {
13391
+ const at = cubic ? (k * 3 + part) * size : k * size;
13392
+ return Array.from(values.subarray(at, at + size));
13393
+ }
13394
+ function sampleChannel(track, values, size, time$2) {
13395
+ const { times } = track;
13396
+ const cubic = track.interpolation === "cubic";
13397
+ const last = times.length - 1;
13398
+ if (time$2 <= (times[0] ?? 0)) return valueAt(values, 0, size, cubic);
13399
+ if (time$2 >= (times[last] ?? 0)) return valueAt(values, last, size, cubic);
13400
+ const k = keyBefore(times, time$2);
13401
+ if (track.interpolation === "step") return valueAt(values, k, size, cubic);
13402
+ const t0 = times[k] ?? 0;
13403
+ const dt = (times[k + 1] ?? t0) - t0;
13404
+ const u = dt > 0 ? (time$2 - t0) / dt : 0;
13405
+ const a = valueAt(values, k, size, cubic);
13406
+ const b = valueAt(values, k + 1, size, cubic);
13407
+ if (cubic) {
13408
+ const outA = valueAt(values, k, size, true, 2);
13409
+ const inB = valueAt(values, k + 1, size, true, 0);
13410
+ const u2 = u * u;
13411
+ const u3 = u2 * u;
13412
+ const out = a.map((p0, i) => (2 * u3 - 3 * u2 + 1) * p0 + (u3 - 2 * u2 + u) * dt * (outA[i] ?? 0) + (-2 * u3 + 3 * u2) * (b[i] ?? 0) + (u3 - u2) * dt * (inB[i] ?? 0));
13413
+ return size === 4 ? normalizeQuat(out) : out;
13414
+ }
13415
+ if (size === 4) return slerp(a, b, u);
13416
+ return a.map((v, i) => v + ((b[i] ?? v) - v) * u);
13417
+ }
13418
+ /** Each node's local matrix with the clip's tracks applied at `time` (seconds). */
13419
+ function localMatrices(nodes, clip, time$2) {
13420
+ const t = nodes.map((n) => [...n.translation]);
13421
+ const r = nodes.map((n) => [...n.rotation]);
13422
+ const s = nodes.map((n) => [...n.scale]);
13423
+ const byName = new Map(nodes.map((n, i) => [n.name, i]));
13424
+ for (const track of clip?.tracks ?? []) {
13425
+ const i = track.node ?? byName.get(track.nodeName);
13426
+ if (i === void 0 || !track.times.length) continue;
13427
+ if (track.translations) t[i] = sampleChannel(track, track.translations, 3, time$2);
13428
+ if (track.rotations) r[i] = sampleChannel(track, track.rotations, 4, time$2);
13429
+ if (track.scales) s[i] = sampleChannel(track, track.scales, 3, time$2);
13430
+ }
13431
+ return nodes.map((n, i) => n.matrix ?? trsMatrix(t[i], r[i], s[i]));
13432
+ }
13433
+ /**
13434
+ * Model-space matrix of every node, posed by `clip` at `time` seconds (the
13435
+ * rest pose without a clip). Parents may follow their children in the list.
13436
+ */
13437
+ function poseNodes(model, clip, time$2) {
13438
+ const nodes = model.nodes ?? [];
13439
+ const local = localMatrices(nodes, clip, time$2);
13440
+ const world = new Array(nodes.length);
13441
+ const resolve$1$1 = (i) => {
13442
+ const done = world[i];
13443
+ if (done) return done;
13444
+ const parent = nodes[i].parent;
13445
+ const m = parent < 0 ? local[i] : Matrix4Math.multiply(resolve$1$1(parent), local[i]);
13446
+ world[i] = m;
13447
+ return m;
13448
+ };
13449
+ return nodes.map((_, i) => resolve$1$1(i));
13450
+ }
13451
+ /** The joint matrices of one skin for a posed scene graph, padded with identity to MAX_JOINTS. */
13452
+ function jointMatrices(model, skinIndex, world) {
13453
+ const skin = model.skins?.[skinIndex];
13454
+ if (!skin) throw new Error(`model has no skin ${skinIndex}`);
13455
+ const out = new Float32Array(MAX_JOINTS * 16);
13456
+ for (let j = 0; j < MAX_JOINTS; j++) {
13457
+ const node = skin.joints[j];
13458
+ if (node === void 0) {
13459
+ out.set(Matrix4Math.identity(), j * 16);
13460
+ continue;
13461
+ }
13462
+ const ibm = Array.from(skin.inverseBindMatrices.subarray(j * 16, j * 16 + 16));
13463
+ out.set(Matrix4Math.multiply(world[node], ibm), j * 16);
13464
+ }
13465
+ return out;
13466
+ }
13467
+ var FbxLoader = class FbxLoader$1 {
13468
+ static FBX_KTIME = 46186158e3;
13469
+ /**
13470
+ * Parses binary or text FBX buffer/string into Model3DData.
13471
+ */
13472
+ static parse(bufferOrString, options = {}) {
13473
+ const rootNode = typeof bufferOrString === "string" ? FbxLoader$1.parseAscii(bufferOrString) : FbxLoader$1.isBinary(bufferOrString) ? FbxLoader$1.parseBinary(bufferOrString) : FbxLoader$1.parseAscii(new TextDecoder().decode(bufferOrString));
13474
+ return FbxLoader$1.buildModelFromTree(rootNode, options);
13475
+ }
13476
+ static isBinary(buffer) {
13477
+ const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
13478
+ if (view.length < 27) return false;
13479
+ const header = "Kaydara FBX Binary \0\0";
13480
+ for (let i = 0; i < 23; i++) if (view[i] !== header.charCodeAt(i)) return false;
13481
+ return true;
13482
+ }
13483
+ /**
13484
+ * Parses Binary FBX 7.x/6.x format.
13485
+ */
13486
+ static parseBinary(buffer) {
13487
+ const u8 = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
13488
+ const view = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
13489
+ const is64Bit = view.getUint32(23, true) >= 7500;
13490
+ let offset = 27;
13491
+ const root = {
13492
+ name: "Root",
13493
+ props: [],
13494
+ children: []
13495
+ };
13496
+ const readNode = () => {
13497
+ if (offset >= u8.byteLength) return null;
13498
+ let endOffset;
13499
+ let numProperties;
13500
+ let nameLen;
13501
+ if (is64Bit) {
13502
+ if (offset + 25 > u8.byteLength) return null;
13503
+ endOffset = Number(view.getBigUint64(offset, true));
13504
+ numProperties = Number(view.getBigUint64(offset + 8, true));
13505
+ nameLen = view.getUint8(offset + 24);
13506
+ offset += 25;
13507
+ } else {
13508
+ if (offset + 13 > u8.byteLength) return null;
13509
+ endOffset = view.getUint32(offset, true);
13510
+ numProperties = view.getUint32(offset + 4, true);
13511
+ nameLen = view.getUint8(offset + 12);
13512
+ offset += 13;
13513
+ }
13514
+ if (endOffset === 0) return null;
13515
+ const name = new TextDecoder().decode(u8.subarray(offset, offset + nameLen));
13516
+ offset += nameLen;
13517
+ const props = [];
13518
+ for (let p = 0; p < numProperties; p++) {
13519
+ const typeCode = String.fromCharCode(u8[offset]);
13520
+ offset += 1;
13521
+ switch (typeCode) {
13522
+ case "Y":
13523
+ props.push(view.getInt16(offset, true));
13524
+ offset += 2;
13525
+ break;
13526
+ case "C":
13527
+ case "B":
13528
+ props.push(view.getUint8(offset) !== 0);
13529
+ offset += 1;
13530
+ break;
13531
+ case "I":
13532
+ props.push(view.getInt32(offset, true));
13533
+ offset += 4;
13534
+ break;
13535
+ case "F":
13536
+ props.push(view.getFloat32(offset, true));
13537
+ offset += 4;
13538
+ break;
13539
+ case "D":
13540
+ props.push(view.getFloat64(offset, true));
13541
+ offset += 8;
13542
+ break;
13543
+ case "L":
13544
+ props.push(Number(view.getBigInt64(offset, true)));
13545
+ offset += 8;
13546
+ break;
13547
+ case "S": {
13548
+ const len = view.getUint32(offset, true);
13549
+ offset += 4;
13550
+ const str = new TextDecoder().decode(u8.subarray(offset, offset + len));
13551
+ props.push(str);
13552
+ offset += len;
13553
+ break;
13554
+ }
13555
+ case "R": {
13556
+ const len = view.getUint32(offset, true);
13557
+ offset += 4;
13558
+ const raw = u8.subarray(offset, offset + len);
13559
+ props.push(raw);
13560
+ offset += len;
13561
+ break;
13562
+ }
13563
+ case "f":
13564
+ case "d":
13565
+ case "i":
13566
+ case "l":
13567
+ case "b":
13568
+ case "c": {
13569
+ const arrayLen = view.getUint32(offset, true);
13570
+ const encoding = view.getUint32(offset + 4, true);
13571
+ const compressedLen = view.getUint32(offset + 8, true);
13572
+ offset += 12;
13573
+ let arrayBuffer;
13574
+ if (encoding === 1) {
13575
+ arrayBuffer = inflateSync(u8.subarray(offset, offset + compressedLen));
13576
+ offset += compressedLen;
13577
+ } else {
13578
+ const totalBytes = arrayLen * (typeCode === "d" || typeCode === "l" ? 8 : typeCode === "f" || typeCode === "i" ? 4 : 1);
13579
+ arrayBuffer = u8.subarray(offset, offset + totalBytes);
13580
+ offset += totalBytes;
13581
+ }
13582
+ const arrayView = new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
13583
+ if (typeCode === "f") {
13584
+ const arr = new Float32Array(arrayLen);
13585
+ for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getFloat32(k * 4, true);
13586
+ props.push(arr);
13587
+ } else if (typeCode === "d") {
13588
+ const arr = new Float64Array(arrayLen);
13589
+ for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getFloat64(k * 8, true);
13590
+ props.push(arr);
13591
+ } else if (typeCode === "i") {
13592
+ const arr = new Int32Array(arrayLen);
13593
+ for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getInt32(k * 4, true);
13594
+ props.push(arr);
13595
+ } else if (typeCode === "l") {
13596
+ const arr = new Float64Array(arrayLen);
13597
+ for (let k = 0; k < arrayLen; k++) arr[k] = Number(arrayView.getBigInt64(k * 8, true));
13598
+ props.push(arr);
13599
+ } else if (typeCode === "b") {
13600
+ const arr = new Uint8Array(arrayLen);
13601
+ for (let k = 0; k < arrayLen; k++) arr[k] = arrayView.getUint8(k);
13602
+ props.push(arr);
13603
+ } else props.push(arrayBuffer);
13604
+ break;
13605
+ }
13606
+ default: break;
13607
+ }
13608
+ }
13609
+ const children = [];
13610
+ while (offset < endOffset) {
13611
+ const child = readNode();
13612
+ if (child) children.push(child);
13613
+ else break;
13614
+ }
13615
+ offset = endOffset;
13616
+ return {
13617
+ name,
13618
+ props,
13619
+ children
13620
+ };
13621
+ };
13622
+ while (offset < u8.byteLength - (is64Bit ? 25 : 13)) {
13623
+ const node = readNode();
13624
+ if (node) root.children.push(node);
13625
+ else break;
13626
+ }
13627
+ return root;
13628
+ }
13629
+ /**
13630
+ * Parses ASCII FBX format.
13631
+ */
13632
+ static parseAscii(text) {
13633
+ const root = {
13634
+ name: "Root",
13635
+ props: [],
13636
+ children: []
13637
+ };
13638
+ const stack = [root];
13639
+ const lines = text.split(/\r?\n/);
13640
+ for (let i = 0; i < lines.length; i++) {
13641
+ const line = lines[i]?.trim();
13642
+ if (!line || line.startsWith(";")) continue;
13643
+ if (line === "}") {
13644
+ if (stack.length > 1) stack.pop();
13645
+ continue;
13646
+ }
13647
+ const colonIdx = line.indexOf(":");
13648
+ if (colonIdx === -1) continue;
13649
+ const name = line.substring(0, colonIdx).trim();
13650
+ const rest = line.substring(colonIdx + 1).trim();
13651
+ const hasBrace = rest.endsWith("{");
13652
+ const propStr = hasBrace ? rest.slice(0, -1).trim() : rest;
13238
13653
  const props = [];
13239
13654
  if (propStr) {
13240
13655
  const rawProps = propStr.split(",").map((p) => p.trim());
@@ -13253,6 +13668,53 @@ var FbxLoader = class FbxLoader$1 {
13253
13668
  }
13254
13669
  return root;
13255
13670
  }
13671
+ /** Material properties a texture can feed, by the FBX (or Maya / 3ds Max) property name. */
13672
+ static textureSlot(prop) {
13673
+ const p = prop.toLowerCase();
13674
+ if (p === "diffusecolor" || p === "diffuse" || p.endsWith("|basecolor") || p.endsWith("|base_color") || p === "basecolor") return "diffuse";
13675
+ if (p === "normalmap" || p.endsWith("|normalcamera") || p.endsWith("|bump_map")) return "normal";
13676
+ }
13677
+ /**
13678
+ * Fills a material's diffuse / normal map from its connected Texture
13679
+ * objects: the bytes embedded in the Video's `Content`, else the file
13680
+ * named by `RelativeFilename` / `FileName`, looked up next to the model.
13681
+ */
13682
+ static attachTextures(material, materialId, objects, connections, basePath) {
13683
+ const stringChild = (node, name) => {
13684
+ const value = node?.children.find((c) => c.name === name)?.props[0];
13685
+ return typeof value === "string" && value ? value : void 0;
13686
+ };
13687
+ for (const c of connections) {
13688
+ if (c.parentId !== materialId || !c.propName) continue;
13689
+ const slot = FbxLoader$1.textureSlot(c.propName);
13690
+ const texture = objects.get(c.childId);
13691
+ if (!slot || texture?.node.name !== "Texture") continue;
13692
+ if (slot === "diffuse" ? material.diffuseTextureBuffer : material.normalTextureBuffer) continue;
13693
+ const video = connections.filter((v) => v.parentId === c.childId && !v.propName).map((v) => objects.get(v.childId)?.node).find((n) => n?.name === "Video");
13694
+ const ref = stringChild(texture.node, "RelativeFilename") ?? stringChild(texture.node, "FileName") ?? stringChild(video, "RelativeFilename") ?? stringChild(video, "Filename");
13695
+ if (ref && !isSupportedTexture(ref)) {
13696
+ warnMissingTexture("FbxLoader", material.name, ref);
13697
+ continue;
13698
+ }
13699
+ let bytes;
13700
+ const content = video?.children.find((n) => n.name === "Content")?.props[0];
13701
+ if (content instanceof Uint8Array && content.length > 0) bytes = content;
13702
+ else if (ref) bytes = readTextureFile(ref, basePath)?.bytes;
13703
+ if (!bytes) {
13704
+ if (ref) warnMissingTexture("FbxLoader", material.name, ref);
13705
+ continue;
13706
+ }
13707
+ if (slot === "diffuse") {
13708
+ material.diffuseTextureBuffer = bytes;
13709
+ material.diffuseTextureMime = textureMime(ref ?? "");
13710
+ if (ref) material.diffuseTexture = ref;
13711
+ material.textureWrap = "repeat";
13712
+ } else {
13713
+ material.normalTextureBuffer = bytes;
13714
+ if (ref) material.normalTexture = ref;
13715
+ }
13716
+ }
13717
+ }
13256
13718
  /**
13257
13719
  * Builds ready-to-render Model3DData from parsed FBX node tree.
13258
13720
  */
@@ -13286,6 +13748,20 @@ var FbxLoader = class FbxLoader$1 {
13286
13748
  });
13287
13749
  }
13288
13750
  }
13751
+ const materialNameForGeometry = (geometryId) => {
13752
+ for (const c of connections) {
13753
+ const mat = c.parentId === geometryId ? objects.get(c.childId) : void 0;
13754
+ if (mat?.node.name === "Material" && !c.propName) return mat.name;
13755
+ }
13756
+ for (const g$1 of connections) {
13757
+ if (g$1.childId !== geometryId || g$1.propName) continue;
13758
+ for (const m of connections) {
13759
+ if (m.parentId !== g$1.parentId || m.propName) continue;
13760
+ const mat = objects.get(m.childId);
13761
+ if (mat?.node.name === "Material") return mat.name;
13762
+ }
13763
+ }
13764
+ };
13289
13765
  const meshes = [];
13290
13766
  const materials = {};
13291
13767
  function extractArrayProp(node) {
@@ -13296,149 +13772,142 @@ var FbxLoader = class FbxLoader$1 {
13296
13772
  if (Array.isArray(node.props) && node.props.length > 0 && typeof node.props[0] === "number") return node.props;
13297
13773
  return [];
13298
13774
  }
13775
+ const modelIds = [...objects.values()].filter((o) => o.node.name === "Model").map((o) => o.id);
13776
+ const nodeIndexById = new Map(modelIds.map((id, i) => [id, i]));
13777
+ const nodes = modelIds.map((id) => {
13778
+ const model$1 = objects.get(id);
13779
+ const parentConn = connections.find((c) => c.childId === id && !c.propName && nodeIndexById.has(c.parentId));
13780
+ return {
13781
+ name: model$1.name,
13782
+ parent: parentConn ? nodeIndexById.get(parentConn.parentId) : -1,
13783
+ translation: readVec3(model$1.node, "Lcl Translation", 0),
13784
+ rotation: modelRotation(model$1.node, fbxEulerToQuat(readVec3(model$1.node, "Lcl Rotation", 0), rotationOrder(model$1.node))),
13785
+ scale: readVec3(model$1.node, "Lcl Scaling", 1)
13786
+ };
13787
+ });
13788
+ const modelOfGeometry = (geometryId) => connections.find((c) => c.childId === geometryId && !c.propName && nodeIndexById.has(c.parentId))?.parentId;
13789
+ const skins = [];
13299
13790
  for (const [id, entry] of objects) if (entry.node.name === "Geometry" && (entry.type === "Mesh" || entry.node.props[2] === "Mesh" || entry.type === "")) {
13300
13791
  const geomNode = entry.node;
13301
13792
  const verticesNode = geomNode.children.find((c) => c.name === "Vertices");
13302
13793
  const indicesNode = geomNode.children.find((c) => c.name === "PolygonVertexIndex");
13303
- const normalLayer = geomNode.children.find((c) => c.name === "LayerElementNormal");
13304
- const uvLayer = geomNode.children.find((c) => c.name === "LayerElementUV");
13305
13794
  if (!verticesNode || !indicesNode) continue;
13306
13795
  const rawVerts = extractArrayProp(verticesNode);
13307
13796
  const rawPolyIndices = extractArrayProp(indicesNode);
13308
- const normalsNode = normalLayer?.children.find((c) => c.name === "Normals");
13309
- const rawNormals = extractArrayProp(normalsNode);
13310
- const uvsNode = uvLayer?.children.find((c) => c.name === "UV");
13311
- const uvIndexNode = uvLayer?.children.find((c) => c.name === "UVIndex");
13312
- const rawUvs = extractArrayProp(uvsNode);
13313
- const rawUvIndices = extractArrayProp(uvIndexNode);
13314
- const triangles = [];
13315
- const triVertexIndexSequence = [];
13797
+ const layer = (name, data, index, size) => {
13798
+ const node = geomNode.children.find((c) => c.name === name);
13799
+ if (!node) return void 0;
13800
+ return layerElement(node, extractArrayProp(node.children.find((c) => c.name === data)), extractArrayProp(node.children.find((c) => c.name === index)), size);
13801
+ };
13802
+ const normalAt = layer("LayerElementNormal", "Normals", "NormalsIndex", 3);
13803
+ const uvAt = layer("LayerElementUV", "UV", "UVIndex", 2);
13804
+ const corners = [];
13316
13805
  let polygon = [];
13806
+ let polyIndex = 0;
13317
13807
  for (let i = 0; i < rawPolyIndices.length; i++) {
13318
13808
  const idx = rawPolyIndices[i];
13319
- if (idx < 0) {
13320
- polygon.push(-(idx + 1));
13321
- triVertexIndexSequence.push(i);
13322
- if (polygon.length >= 3) for (let k = 1; k < polygon.length - 1; k++) triangles.push([
13323
- polygon[0],
13324
- polygon[k],
13325
- polygon[k + 1]
13326
- ]);
13327
- polygon = [];
13328
- } else {
13329
- polygon.push(idx);
13330
- triVertexIndexSequence.push(i);
13331
- }
13332
- }
13333
- const outPositions = [];
13334
- const outNormals = [];
13335
- const outUvs = [];
13336
- const outIndices = [];
13337
- for (let t = 0; t < triangles.length; t++) {
13338
- const tri = triangles[t];
13339
- for (let v = 0; v < 3; v++) {
13340
- const vertIdx = tri[v];
13341
- const outIdx = outPositions.length / 3;
13342
- const px = rawVerts[vertIdx * 3] ?? 0;
13343
- const py = rawVerts[vertIdx * 3 + 1] ?? 0;
13344
- const pz = rawVerts[vertIdx * 3 + 2] ?? 0;
13345
- outPositions.push(px, py, pz);
13346
- if (rawNormals.length > 0) {
13347
- const nx = rawNormals[vertIdx * 3] ?? 0;
13348
- const ny = rawNormals[vertIdx * 3 + 1] ?? 0;
13349
- const nz = rawNormals[vertIdx * 3 + 2] ?? 0;
13350
- outNormals.push(nx, ny, nz);
13351
- } else outNormals.push(0, 0, 1);
13352
- if (rawUvs.length > 0) {
13353
- let uvIdx = vertIdx;
13354
- if (rawUvIndices.length > 0) uvIdx = rawUvIndices[vertIdx] ?? vertIdx;
13355
- const u = rawUvs[uvIdx * 2] ?? 0;
13356
- const v$1 = rawUvs[uvIdx * 2 + 1] ?? 0;
13357
- outUvs.push(u, 1 - v$1);
13358
- } else outUvs.push(0, 0);
13359
- outIndices.push(outIdx);
13360
- }
13809
+ polygon.push({
13810
+ cp: idx < 0 ? -(idx + 1) : idx,
13811
+ pv: i
13812
+ });
13813
+ if (idx >= 0) continue;
13814
+ for (let k = 1; k < polygon.length - 1; k++) for (const c of [
13815
+ polygon[0],
13816
+ polygon[k],
13817
+ polygon[k + 1]
13818
+ ]) corners.push({
13819
+ ...c,
13820
+ poly: polyIndex
13821
+ });
13822
+ polygon = [];
13823
+ polyIndex++;
13361
13824
  }
13362
- if (rawNormals.length === 0 || options.smoothNormals) {
13363
- const computedNormals = new Float32Array(outPositions.length);
13364
- for (let i = 0; i < outIndices.length; i += 3) {
13365
- const i0 = outIndices[i];
13366
- const i1 = outIndices[i + 1];
13367
- const i2 = outIndices[i + 2];
13368
- const p0 = [
13369
- outPositions[i0 * 3],
13370
- outPositions[i0 * 3 + 1],
13371
- outPositions[i0 * 3 + 2]
13372
- ];
13373
- const p1 = [
13374
- outPositions[i1 * 3],
13375
- outPositions[i1 * 3 + 1],
13376
- outPositions[i1 * 3 + 2]
13377
- ];
13378
- const p2 = [
13379
- outPositions[i2 * 3],
13380
- outPositions[i2 * 3 + 1],
13381
- outPositions[i2 * 3 + 2]
13382
- ];
13383
- const edge1 = Vector3Math.subtract(p1, p0);
13384
- const edge2 = Vector3Math.subtract(p2, p0);
13385
- const faceNormal = Vector3Math.cross(edge1, edge2);
13386
- computedNormals[i0 * 3] += faceNormal[0];
13387
- computedNormals[i0 * 3 + 1] += faceNormal[1];
13388
- computedNormals[i0 * 3 + 2] += faceNormal[2];
13389
- computedNormals[i1 * 3] += faceNormal[0];
13390
- computedNormals[i1 * 3 + 1] += faceNormal[1];
13391
- computedNormals[i1 * 3 + 2] += faceNormal[2];
13392
- computedNormals[i2 * 3] += faceNormal[0];
13393
- computedNormals[i2 * 3 + 1] += faceNormal[1];
13394
- computedNormals[i2 * 3 + 2] += faceNormal[2];
13395
- }
13396
- for (let v = 0; v < outPositions.length / 3; v++) {
13397
- const nx = computedNormals[v * 3];
13398
- const ny = computedNormals[v * 3 + 1];
13399
- const nz = computedNormals[v * 3 + 2];
13400
- const len = Math.hypot(nx, ny, nz) || 1;
13401
- outNormals[v * 3] = nx / len;
13402
- outNormals[v * 3 + 1] = ny / len;
13403
- outNormals[v * 3 + 2] = nz / len;
13404
- }
13825
+ const numVerts = corners.length;
13826
+ const outPositions = new Float32Array(numVerts * 3);
13827
+ const outNormals = new Float32Array(numVerts * 3);
13828
+ const outUvs = new Float32Array(numVerts * 2);
13829
+ const outIndices = new Array(numVerts);
13830
+ corners.forEach(({ cp, pv, poly }, v) => {
13831
+ outPositions[v * 3] = rawVerts[cp * 3] ?? 0;
13832
+ outPositions[v * 3 + 1] = rawVerts[cp * 3 + 1] ?? 0;
13833
+ outPositions[v * 3 + 2] = rawVerts[cp * 3 + 2] ?? 0;
13834
+ const n = normalAt?.(cp, pv, poly);
13835
+ outNormals.set(n ?? [
13836
+ 0,
13837
+ 0,
13838
+ 1
13839
+ ], v * 3);
13840
+ const uv = uvAt?.(cp, pv, poly);
13841
+ outUvs[v * 2] = uv?.[0] ?? 0;
13842
+ outUvs[v * 2 + 1] = 1 - (uv?.[1] ?? 0);
13843
+ outIndices[v] = v;
13844
+ });
13845
+ if (!normalAt || options.smoothNormals) {
13846
+ outNormals.fill(0);
13847
+ accumulateVertexNormals(outPositions, outIndices, outNormals);
13405
13848
  }
13406
- const skinConn = connections.find((c) => c.parentId === id && objects.get(c.childId)?.type === "Deformer" && objects.get(c.childId)?.node.props[2] === "Skin");
13849
+ const skinConn = connections.find((c) => c.parentId === id && objects.get(c.childId)?.node.name === "Deformer" && objects.get(c.childId)?.node.props[2] === "Skin");
13407
13850
  let jointIndices;
13408
13851
  let jointWeights;
13852
+ let skinIndex;
13409
13853
  if (skinConn) {
13410
- const numVerts = outPositions.length / 3;
13411
- jointIndices = new Float32Array(numVerts * 4);
13412
- jointWeights = new Float32Array(numVerts * 4);
13413
- connections.filter((c) => c.parentId === skinConn.childId && objects.get(c.childId)?.type === "Deformer" && objects.get(c.childId)?.node.props[2] === "Cluster").forEach((cc, boneIdx) => {
13414
- const clusterNode = objects.get(cc.childId)?.node;
13415
- const indexesNode = clusterNode?.children.find((c) => c.name === "Indexes");
13416
- const weightsNode = clusterNode?.children.find((c) => c.name === "Weights");
13417
- if (indexesNode && weightsNode) {
13418
- const cIndexes = extractArrayProp(indexesNode);
13419
- const cWeights = extractArrayProp(weightsNode);
13420
- for (let k = 0; k < cIndexes.length; k++) {
13421
- const weight = cWeights[k] ?? 0;
13422
- for (let v = 0; v < numVerts; v++) for (let slot = 0; slot < 4; slot++) if (jointWeights[v * 4 + slot] === 0) {
13423
- jointIndices[v * 4 + slot] = boneIdx;
13424
- jointWeights[v * 4 + slot] = weight;
13425
- break;
13426
- }
13427
- }
13854
+ const joints = [];
13855
+ const inverseBinds = [];
13856
+ const influences = /* @__PURE__ */ new Map();
13857
+ for (const cc of connections) {
13858
+ const cluster = objects.get(cc.childId);
13859
+ if (cc.parentId !== skinConn.childId || cluster?.node.name !== "Deformer" || cluster.node.props[2] !== "Cluster") continue;
13860
+ const boneId = connections.find((b) => b.parentId === cluster.id && nodeIndexById.has(b.childId))?.childId;
13861
+ if (boneId === void 0) continue;
13862
+ const joint = joints.length;
13863
+ joints.push(nodeIndexById.get(boneId));
13864
+ const transform$1 = matrixChild(cluster.node, "Transform");
13865
+ const link = matrixChild(cluster.node, "TransformLink");
13866
+ inverseBinds.push(...Matrix4Math.multiply(Matrix4Math.invert(link) ?? Matrix4Math.identity(), transform$1));
13867
+ const cpIndexes = extractArrayProp(cluster.node.children.find((c) => c.name === "Indexes"));
13868
+ const cpWeights = extractArrayProp(cluster.node.children.find((c) => c.name === "Weights"));
13869
+ for (let k = 0; k < cpIndexes.length; k++) {
13870
+ const weight = Number(cpWeights[k] ?? 0);
13871
+ if (!(weight > 0)) continue;
13872
+ const cp = Number(cpIndexes[k]);
13873
+ const list = influences.get(cp) ?? [];
13874
+ list.push([joint, weight]);
13875
+ influences.set(cp, list);
13428
13876
  }
13429
- });
13877
+ }
13878
+ if (joints.length > MAX_JOINTS) throw new Error(`FBX skin has ${joints.length} bones; the mesh shader holds ${MAX_JOINTS}`);
13879
+ if (joints.length > 0) {
13880
+ skinIndex = skins.length;
13881
+ skins.push({
13882
+ joints,
13883
+ inverseBindMatrices: new Float32Array(inverseBinds)
13884
+ });
13885
+ jointIndices = new Float32Array(numVerts * 4);
13886
+ jointWeights = new Float32Array(numVerts * 4);
13887
+ corners.forEach(({ cp }, v) => {
13888
+ const top = (influences.get(cp) ?? []).sort((a, b) => b[1] - a[1]).slice(0, 4);
13889
+ const total = top.reduce((sum, [, w]) => sum + w, 0);
13890
+ top.forEach(([joint, weight], slot) => {
13891
+ jointIndices[v * 4 + slot] = joint;
13892
+ jointWeights[v * 4 + slot] = total > 0 ? weight / total : 0;
13893
+ });
13894
+ });
13895
+ }
13430
13896
  }
13431
- const isLarge = outPositions.length / 3 > 65535;
13897
+ const modelId = modelOfGeometry(id);
13898
+ const isLarge = numVerts > 65535;
13432
13899
  meshes.push({
13433
13900
  id: `fbx_mesh_${id}`,
13434
13901
  name: entry.name,
13435
- positions: new Float32Array(outPositions),
13436
- normals: new Float32Array(outNormals),
13437
- uvs: new Float32Array(outUvs),
13902
+ positions: outPositions,
13903
+ normals: outNormals,
13904
+ uvs: outUvs,
13438
13905
  indices: isLarge ? new Uint32Array(outIndices) : new Uint16Array(outIndices),
13439
13906
  jointIndices,
13440
13907
  jointWeights,
13441
- materialName: entry.name
13908
+ materialName: materialNameForGeometry(id) ?? entry.name,
13909
+ node: modelId !== void 0 ? nodeIndexById.get(modelId) : void 0,
13910
+ skin: skinIndex
13442
13911
  });
13443
13912
  }
13444
13913
  for (const [, entry] of objects) if (entry.node.name === "Material") {
@@ -13467,12 +13936,14 @@ var FbxLoader = class FbxLoader$1 {
13467
13936
  }
13468
13937
  }
13469
13938
  }
13470
- materials[entry.name] = {
13939
+ const material = {
13471
13940
  name: entry.name,
13472
13941
  diffuseColor,
13473
13942
  shininess,
13474
13943
  opacity
13475
13944
  };
13945
+ FbxLoader$1.attachTextures(material, entry.id, objects, connections, options.basePath);
13946
+ materials[entry.name] = material;
13476
13947
  }
13477
13948
  const animations = [];
13478
13949
  for (const [id, entry] of objects) if (entry.node.name === "AnimationStack") {
@@ -13492,12 +13963,13 @@ var FbxLoader = class FbxLoader$1 {
13492
13963
  const key = `${targetModel?.id ?? curveNodeObj.id}|${kind}`;
13493
13964
  let group = groups.get(key);
13494
13965
  if (!group) {
13495
- const model = targetModel?.node;
13966
+ const model$1 = targetModel?.node;
13496
13967
  group = {
13497
13968
  nodeName: targetModel?.name ?? curveNodeObj.name,
13498
- model,
13969
+ modelId: targetModel?.id,
13970
+ model: model$1,
13499
13971
  kind,
13500
- defaults: staticTransform(model, curveNodeObj.node, kind),
13972
+ defaults: staticTransform(model$1, curveNodeObj.node, kind),
13501
13973
  channels: [
13502
13974
  void 0,
13503
13975
  void 0,
@@ -13522,7 +13994,13 @@ var FbxLoader = class FbxLoader$1 {
13522
13994
  }
13523
13995
  for (const group of groups.values()) {
13524
13996
  const track = buildTransformTrack(group);
13525
- if (track) tracks.push(track);
13997
+ if (!track) continue;
13998
+ if (group.modelId !== void 0) track.node = nodeIndexById.get(group.modelId);
13999
+ if (track.rotations && group.model) for (let k = 0; k < track.rotations.length; k += 4) {
14000
+ const q = Array.from(track.rotations.subarray(k, k + 4));
14001
+ track.rotations.set(modelRotation(group.model, q), k);
14002
+ }
14003
+ tracks.push(track);
13526
14004
  }
13527
14005
  }
13528
14006
  let maxDuration = 1;
@@ -13533,72 +14011,176 @@ var FbxLoader = class FbxLoader$1 {
13533
14011
  animations.push({
13534
14012
  name: stackName,
13535
14013
  duration: maxDuration,
13536
- fps: 30,
13537
- tracks
13538
- });
13539
- }
13540
- let minX = Infinity;
13541
- let minY = Infinity;
13542
- let minZ = Infinity;
13543
- let maxX = -Infinity;
13544
- let maxY = -Infinity;
13545
- let maxZ = -Infinity;
13546
- for (const mesh of meshes) for (let i = 0; i < mesh.positions.length; i += 3) {
13547
- const x = mesh.positions[i];
13548
- const y = mesh.positions[i + 1];
13549
- const z = mesh.positions[i + 2];
13550
- if (x < minX) minX = x;
13551
- if (y < minY) minY = y;
13552
- if (z < minZ) minZ = z;
13553
- if (x > maxX) maxX = x;
13554
- if (y > maxY) maxY = y;
13555
- if (z > maxZ) maxZ = z;
13556
- }
13557
- if (!Number.isFinite(minX)) {
13558
- minX = -1;
13559
- minY = -1;
13560
- minZ = -1;
13561
- maxX = 1;
13562
- maxY = 1;
13563
- maxZ = 1;
14014
+ fps: 30,
14015
+ tracks
14016
+ });
13564
14017
  }
13565
- const sizeX = maxX - minX;
13566
- const sizeY = maxY - minY;
13567
- const sizeZ = maxZ - minZ;
13568
- const centerX = (minX + maxX) * .5;
13569
- const centerY = (minY + maxY) * .5;
13570
- const centerZ = (minZ + maxZ) * .5;
13571
- const sphereRadius = Math.hypot(sizeX, sizeY, sizeZ) * .5;
13572
- return {
14018
+ const model = {
13573
14019
  meshes,
13574
14020
  materials,
13575
14021
  animations,
14022
+ nodes,
14023
+ skins,
13576
14024
  bounds: {
13577
14025
  min: [
13578
- minX,
13579
- minY,
13580
- minZ
14026
+ 0,
14027
+ 0,
14028
+ 0
13581
14029
  ],
13582
14030
  max: [
13583
- maxX,
13584
- maxY,
13585
- maxZ
14031
+ 0,
14032
+ 0,
14033
+ 0
13586
14034
  ],
13587
14035
  center: [
13588
- centerX,
13589
- centerY,
13590
- centerZ
14036
+ 0,
14037
+ 0,
14038
+ 0
13591
14039
  ],
13592
14040
  size: [
13593
- sizeX,
13594
- sizeY,
13595
- sizeZ
14041
+ 0,
14042
+ 0,
14043
+ 0
13596
14044
  ],
13597
- boundingSphereRadius: sphereRadius
14045
+ boundingSphereRadius: 0
14046
+ }
14047
+ };
14048
+ const rest = poseNodes(model, void 0, 0);
14049
+ const lo = [
14050
+ Infinity,
14051
+ Infinity,
14052
+ Infinity
14053
+ ];
14054
+ const hi = [
14055
+ -Infinity,
14056
+ -Infinity,
14057
+ -Infinity
14058
+ ];
14059
+ for (const mesh of meshes) {
14060
+ const place = mesh.node !== void 0 ? rest[mesh.node] : void 0;
14061
+ for (let i = 0; i < mesh.positions.length; i += 3) {
14062
+ const p = [
14063
+ mesh.positions[i],
14064
+ mesh.positions[i + 1],
14065
+ mesh.positions[i + 2]
14066
+ ];
14067
+ const q = place ? Matrix4Math.projectPoint(place, p) : p;
14068
+ for (let a = 0; a < 3; a++) {
14069
+ if (q[a] < lo[a]) lo[a] = q[a];
14070
+ if (q[a] > hi[a]) hi[a] = q[a];
14071
+ }
13598
14072
  }
14073
+ }
14074
+ if (!Number.isFinite(lo[0])) {
14075
+ lo.fill(-1);
14076
+ hi.fill(1);
14077
+ }
14078
+ const { offset, scale } = resolveModelTransform({
14079
+ min: lo,
14080
+ max: hi,
14081
+ center: [
14082
+ (lo[0] + hi[0]) * .5,
14083
+ (lo[1] + hi[1]) * .5,
14084
+ (lo[2] + hi[2]) * .5
14085
+ ],
14086
+ size: [
14087
+ hi[0] - lo[0],
14088
+ hi[1] - lo[1],
14089
+ hi[2] - lo[2]
14090
+ ]
14091
+ }, options);
14092
+ model.rootTransform = Matrix4Math.multiply(Matrix4Math.scale(scale[0], scale[1], scale[2]), Matrix4Math.translate(-offset[0], -offset[1], -offset[2]));
14093
+ const min = [
14094
+ 0,
14095
+ 1,
14096
+ 2
14097
+ ].map((a) => (lo[a] - offset[a]) * scale[a]);
14098
+ const max = [
14099
+ 0,
14100
+ 1,
14101
+ 2
14102
+ ].map((a) => (hi[a] - offset[a]) * scale[a]);
14103
+ model.bounds = {
14104
+ min,
14105
+ max,
14106
+ center: [
14107
+ (min[0] + max[0]) * .5,
14108
+ (min[1] + max[1]) * .5,
14109
+ (min[2] + max[2]) * .5
14110
+ ],
14111
+ size: [
14112
+ max[0] - min[0],
14113
+ max[1] - min[1],
14114
+ max[2] - min[2]
14115
+ ],
14116
+ boundingSphereRadius: Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) * .5
13599
14117
  };
14118
+ return model;
13600
14119
  }
13601
14120
  };
14121
+ /** A Properties70 vector (Lcl Translation / Rotation / Scaling), `fallback` where absent. */
14122
+ function readVec3(node, name, fallback) {
14123
+ const v = readProperty70(node, name);
14124
+ return [
14125
+ 0,
14126
+ 1,
14127
+ 2
14128
+ ].map((a) => {
14129
+ const x = v?.[a];
14130
+ return x !== void 0 && Number.isFinite(x) ? x : fallback;
14131
+ });
14132
+ }
14133
+ function multiplyQuat(a, b) {
14134
+ return [
14135
+ a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
14136
+ a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
14137
+ a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
14138
+ a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2]
14139
+ ];
14140
+ }
14141
+ /**
14142
+ * A Model's full local rotation for an Lcl Rotation `q`:
14143
+ * PreRotation · q · PostRotation⁻¹ (FBX always applies pre/post in XYZ order).
14144
+ */
14145
+ function modelRotation(model, q) {
14146
+ const pre = readProperty70(model, "PreRotation");
14147
+ const post = readProperty70(model, "PostRotation");
14148
+ let out = q;
14149
+ if (pre) out = multiplyQuat(fbxEulerToQuat(readVec3(model, "PreRotation", 0)), out);
14150
+ if (post) {
14151
+ const p = fbxEulerToQuat(readVec3(model, "PostRotation", 0));
14152
+ out = multiplyQuat(out, [
14153
+ -p[0],
14154
+ -p[1],
14155
+ -p[2],
14156
+ p[3]
14157
+ ]);
14158
+ }
14159
+ return out;
14160
+ }
14161
+ /** A 4×4 matrix child (`Transform: *16 { a: … }`), identity when absent. */
14162
+ function matrixChild(node, name) {
14163
+ const child = node.children.find((c) => c.name === name);
14164
+ const raw = child?.children.find((c) => c.name === "a")?.props ?? child?.props;
14165
+ const values = raw?.[0] instanceof Float64Array || raw?.[0] instanceof Float32Array ? Array.from(raw[0]) : (raw ?? []).map(Number);
14166
+ return values.length === 16 ? values : Matrix4Math.identity();
14167
+ }
14168
+ /**
14169
+ * A geometry layer (normals, UVs) as a lookup by corner: its mapping says
14170
+ * whether values are per control point, per polygon vertex, per polygon or
14171
+ * one for all; IndexToDirect layers go through their index array.
14172
+ */
14173
+ function layerElement(layer, data, index, size) {
14174
+ if (data.length === 0) return void 0;
14175
+ const text = (name) => String(layer.children.find((c) => c.name === name)?.props[0] ?? "");
14176
+ const mapping = text("MappingInformationType");
14177
+ const indexed = /^Index/.test(text("ReferenceInformationType")) && index.length > 0;
14178
+ return (cp, pv, poly) => {
14179
+ let i = mapping === "ByPolygonVertex" ? pv : mapping === "ByPolygon" ? poly : mapping === "AllSame" ? 0 : cp;
14180
+ if (indexed) i = Number(index[i] ?? i);
14181
+ return Array.from({ length: size }, (_, c) => Number(data[i * size + c] ?? 0));
14182
+ };
14183
+ }
13602
14184
  /**
13603
14185
  * Classifies a curve node from its OP connection property ("Lcl Translation",
13604
14186
  * "Lcl Rotation", "Lcl Scaling") or, failing that, its short name ("T", "R", "S").
@@ -13728,193 +14310,46 @@ function buildTransformTrack(group) {
13728
14310
  return channel ? sampleCurve(channel, key) : group.defaults[axis];
13729
14311
  });
13730
14312
  if (!isRotation) {
13731
- values.set(v, k * 3);
13732
- return;
13733
- }
13734
- const q = fbxEulerToQuat(v, order);
13735
- if (prev && prev[0] * q[0] + prev[1] * q[1] + prev[2] * q[2] + prev[3] * q[3] < 0) for (let i = 0; i < 4; i++) q[i] = -q[i];
13736
- prev = q;
13737
- values.set(q, k * 4);
13738
- });
13739
- return {
13740
- nodeName: group.nodeName,
13741
- times,
13742
- translations: group.kind === "T" ? values : void 0,
13743
- rotations: isRotation ? values : void 0,
13744
- scales: group.kind === "S" ? values : void 0
13745
- };
13746
- }
13747
- function parseFBX(bufferOrText, options = {}) {
13748
- return FbxLoader.parse(bufferOrText, options);
13749
- }
13750
- function loadFBX(srcOrData, options = {}) {
13751
- let data = srcOrData;
13752
- if (typeof srcOrData === "string") {
13753
- const trimmed = srcOrData.trim();
13754
- if (!(trimmed.includes("FBXHeaderExtension") || trimmed.includes("Kaydara FBX")) && typeof fs !== "undefined" && fs.existsSync) try {
13755
- let filePath = srcOrData.startsWith("file://") ? srcOrData.slice(7) : srcOrData;
13756
- try {
13757
- filePath = decodeURIComponent(filePath);
13758
- } catch (_) {}
13759
- if (fs.existsSync(filePath)) {
13760
- const buf = fs.readFileSync(filePath);
13761
- data = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
13762
- }
13763
- } catch (_) {}
13764
- }
13765
- return FbxLoader.parse(data, options);
13766
- }
13767
- /**
13768
- * Adds each triangle's (area-weighted) face normal to its three vertices, then
13769
- * normalizes every vertex normal in place.
13770
- */
13771
- function accumulateVertexNormals(positions, indices, normals) {
13772
- const vertex = (i) => [
13773
- positions[i * 3],
13774
- positions[i * 3 + 1],
13775
- positions[i * 3 + 2]
13776
- ];
13777
- for (let i = 0; i < indices.length; i += 3) {
13778
- const i0 = indices[i];
13779
- const i1 = indices[i + 1];
13780
- const i2 = indices[i + 2];
13781
- const p0 = vertex(i0);
13782
- const fn = Vector3Math.cross(Vector3Math.subtract(vertex(i1), p0), Vector3Math.subtract(vertex(i2), p0));
13783
- for (const v of [
13784
- i0,
13785
- i1,
13786
- i2
13787
- ]) {
13788
- normals[v * 3] += fn[0];
13789
- normals[v * 3 + 1] += fn[1];
13790
- normals[v * 3 + 2] += fn[2];
13791
- }
13792
- }
13793
- for (let i = 0; i < normals.length; i += 3) {
13794
- const nx = normals[i];
13795
- const ny = normals[i + 1];
13796
- const nz = normals[i + 2];
13797
- const len = Math.hypot(nx, ny, nz) || 1;
13798
- normals[i] = nx / len;
13799
- normals[i + 1] = ny / len;
13800
- normals[i + 2] = nz / len;
13801
- }
13802
- }
13803
- /** Axis-aligned bounds of xyz position arrays (all zero when empty). */
13804
- function computeBounds(...positionArrays) {
13805
- let minX = Infinity, minY = Infinity, minZ = Infinity;
13806
- let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
13807
- for (const positions of positionArrays) for (let i = 0; i < positions.length; i += 3) {
13808
- const x = positions[i];
13809
- const y = positions[i + 1];
13810
- const z = positions[i + 2];
13811
- if (x < minX) minX = x;
13812
- if (x > maxX) maxX = x;
13813
- if (y < minY) minY = y;
13814
- if (y > maxY) maxY = y;
13815
- if (z < minZ) minZ = z;
13816
- if (z > maxZ) maxZ = z;
13817
- }
13818
- if (!Number.isFinite(minX)) {
13819
- minX = minY = minZ = 0;
13820
- maxX = maxY = maxZ = 0;
13821
- }
13822
- return {
13823
- min: [
13824
- minX,
13825
- minY,
13826
- minZ
13827
- ],
13828
- max: [
13829
- maxX,
13830
- maxY,
13831
- maxZ
13832
- ],
13833
- center: [
13834
- (minX + maxX) * .5,
13835
- (minY + maxY) * .5,
13836
- (minZ + maxZ) * .5
13837
- ],
13838
- size: [
13839
- maxX - minX,
13840
- maxY - minY,
13841
- maxZ - minZ
13842
- ]
13843
- };
13844
- }
13845
- /**
13846
- * The placement `LoadModelOptions` asks for: recentring on the bounds
13847
- * (`center`), fitting the largest side to `normalizeSize`, then `scale`.
13848
- */
13849
- function resolveModelTransform(bounds, options) {
13850
- const maxDimension = Math.max(...bounds.size, 1e-4);
13851
- let scaleMultiplier = 1;
13852
- if (options.normalizeSize && options.normalizeSize > 0) scaleMultiplier = options.normalizeSize / maxDimension;
13853
- const scale = [
13854
- scaleMultiplier,
13855
- scaleMultiplier,
13856
- scaleMultiplier
13857
- ];
13858
- if (typeof options.scale === "number") {
13859
- scale[0] *= options.scale;
13860
- scale[1] *= options.scale;
13861
- scale[2] *= options.scale;
13862
- } else if (Array.isArray(options.scale)) {
13863
- scale[0] *= options.scale[0];
13864
- scale[1] *= options.scale[1];
13865
- scale[2] *= options.scale[2];
13866
- }
13867
- return {
13868
- offset: options.center ? bounds.center : [
13869
- 0,
13870
- 0,
13871
- 0
13872
- ],
13873
- scale
13874
- };
13875
- }
13876
- /** `(p - offset) * scale` for every xyz position. */
13877
- function transformPositions(positions, { offset, scale }) {
13878
- const out = new Float32Array(positions.length);
13879
- for (let i = 0; i < positions.length; i += 3) {
13880
- out[i] = (positions[i] - offset[0]) * scale[0];
13881
- out[i + 1] = (positions[i + 1] - offset[1]) * scale[1];
13882
- out[i + 2] = (positions[i + 2] - offset[2]) * scale[2];
13883
- }
13884
- return out;
13885
- }
13886
- /** Uint16 indices while every vertex fits, Uint32 beyond 65535 vertices. */
13887
- function createIndexArray(indices, vertexCount) {
13888
- return vertexCount > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
13889
- }
13890
- /** The model's bounds after `transform`, as `Model3DData.bounds` reports them. */
13891
- function transformModelBounds(bounds, { offset, scale }) {
13892
- const min = [
13893
- (bounds.min[0] - offset[0]) * scale[0],
13894
- (bounds.min[1] - offset[1]) * scale[1],
13895
- (bounds.min[2] - offset[2]) * scale[2]
13896
- ];
13897
- const max = [
13898
- (bounds.max[0] - offset[0]) * scale[0],
13899
- (bounds.max[1] - offset[1]) * scale[1],
13900
- (bounds.max[2] - offset[2]) * scale[2]
13901
- ];
14313
+ values.set(v, k * 3);
14314
+ return;
14315
+ }
14316
+ const q = fbxEulerToQuat(v, order);
14317
+ if (prev && prev[0] * q[0] + prev[1] * q[1] + prev[2] * q[2] + prev[3] * q[3] < 0) for (let i = 0; i < 4; i++) q[i] = -q[i];
14318
+ prev = q;
14319
+ values.set(q, k * 4);
14320
+ });
13902
14321
  return {
13903
- min,
13904
- max,
13905
- center: [
13906
- (min[0] + max[0]) * .5,
13907
- (min[1] + max[1]) * .5,
13908
- (min[2] + max[2]) * .5
13909
- ],
13910
- size: [
13911
- max[0] - min[0],
13912
- max[1] - min[1],
13913
- max[2] - min[2]
13914
- ],
13915
- boundingSphereRadius: Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) * .5
14322
+ nodeName: group.nodeName,
14323
+ times,
14324
+ translations: group.kind === "T" ? values : void 0,
14325
+ rotations: isRotation ? values : void 0,
14326
+ scales: group.kind === "S" ? values : void 0
13916
14327
  };
13917
14328
  }
14329
+ function parseFBX(bufferOrText, options = {}) {
14330
+ return FbxLoader.parse(bufferOrText, options);
14331
+ }
14332
+ function loadFBX(srcOrData, options = {}) {
14333
+ let data = srcOrData;
14334
+ if (typeof srcOrData === "string") {
14335
+ const trimmed = srcOrData.trim();
14336
+ if (!(trimmed.includes("FBXHeaderExtension") || trimmed.includes("Kaydara FBX")) && typeof fs !== "undefined" && fs.existsSync) try {
14337
+ let filePath = srcOrData.startsWith("file://") ? srcOrData.slice(7) : srcOrData;
14338
+ try {
14339
+ filePath = decodeURIComponent(filePath);
14340
+ } catch (_) {}
14341
+ if (fs.existsSync(filePath)) {
14342
+ const buf = fs.readFileSync(filePath);
14343
+ data = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
14344
+ options = {
14345
+ basePath: path.dirname(filePath),
14346
+ ...options
14347
+ };
14348
+ }
14349
+ } catch (_) {}
14350
+ }
14351
+ return FbxLoader.parse(data, options);
14352
+ }
13918
14353
  var StlLoader = class StlLoader$1 {
13919
14354
  /**
13920
14355
  * Checks if buffer is Binary STL or ASCII STL.
@@ -14384,7 +14819,12 @@ var PlyLoader = class PlyLoader$1 {
14384
14819
  const indexArray = createIndexArray(rawIndices, positions.length / 3);
14385
14820
  const defaultMaterial = {
14386
14821
  name: "ply_default",
14387
- diffuseColor: [
14822
+ diffuseColor: rawColors ? [
14823
+ 1,
14824
+ 1,
14825
+ 1,
14826
+ 1
14827
+ ] : [
14388
14828
  .8,
14389
14829
  .8,
14390
14830
  .85,
@@ -14427,164 +14867,6 @@ var PlyLoader = class PlyLoader$1 {
14427
14867
  function loadPLY(srcOrData, options = {}) {
14428
14868
  return PlyLoader.load(srcOrData, options);
14429
14869
  }
14430
- /**
14431
- * Poses a model's scene graph (glTF semantics): samples an animation clip's
14432
- * translation / rotation / scale tracks at a time, composes the node
14433
- * hierarchy into model-space matrices, and turns a skin's joints into the
14434
- * joint matrices the mesh shader blends (joint world × inverse bind).
14435
- */
14436
- /** Joints per skin the mesh shader holds (mesh3d.wgsl jointMatrices). */
14437
- const MAX_JOINTS = 128;
14438
- /** Column-major translate · rotate(quaternion) · scale. */
14439
- function trsMatrix(t, q, s) {
14440
- const [x, y, z, w] = q;
14441
- const xx = x * x;
14442
- const yy = y * y;
14443
- const zz = z * z;
14444
- const xy = x * y;
14445
- const xz = x * z;
14446
- const yz = y * z;
14447
- const wx = w * x;
14448
- const wy = w * y;
14449
- const wz = w * z;
14450
- return [
14451
- (1 - 2 * (yy + zz)) * s[0],
14452
- 2 * (xy + wz) * s[0],
14453
- 2 * (xz - wy) * s[0],
14454
- 0,
14455
- 2 * (xy - wz) * s[1],
14456
- (1 - 2 * (xx + zz)) * s[1],
14457
- 2 * (yz + wx) * s[1],
14458
- 0,
14459
- 2 * (xz + wy) * s[2],
14460
- 2 * (yz - wx) * s[2],
14461
- (1 - 2 * (xx + yy)) * s[2],
14462
- 0,
14463
- t[0],
14464
- t[1],
14465
- t[2],
14466
- 1
14467
- ];
14468
- }
14469
- function normalizeQuat(q) {
14470
- const len = Math.hypot(q[0], q[1], q[2], q[3]) || 1;
14471
- return [
14472
- q[0] / len,
14473
- q[1] / len,
14474
- q[2] / len,
14475
- q[3] / len
14476
- ];
14477
- }
14478
- function slerp(a, b, t) {
14479
- let cos = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
14480
- const sign = cos < 0 ? -1 : 1;
14481
- cos *= sign;
14482
- if (cos > .9995) return normalizeQuat([
14483
- a[0] + (sign * b[0] - a[0]) * t,
14484
- a[1] + (sign * b[1] - a[1]) * t,
14485
- a[2] + (sign * b[2] - a[2]) * t,
14486
- a[3] + (sign * b[3] - a[3]) * t
14487
- ]);
14488
- const theta = Math.acos(cos);
14489
- const sin = Math.sin(theta);
14490
- const wa = Math.sin((1 - t) * theta) / sin;
14491
- const wb = sign * Math.sin(t * theta) / sin;
14492
- return [
14493
- a[0] * wa + b[0] * wb,
14494
- a[1] * wa + b[1] * wb,
14495
- a[2] * wa + b[2] * wb,
14496
- a[3] * wa + b[3] * wb
14497
- ];
14498
- }
14499
- /** Index of the last keyframe at or before `time` (binary search). */
14500
- function keyBefore(times, time$2) {
14501
- let lo = 0;
14502
- let hi = times.length - 1;
14503
- while (lo < hi) {
14504
- const mid = lo + hi + 1 >> 1;
14505
- if ((times[mid] ?? 0) <= time$2) lo = mid;
14506
- else hi = mid - 1;
14507
- }
14508
- return lo;
14509
- }
14510
- /** Element `k` (of `size` components) of a track's values; cubic tracks store [in, value, out] triples. */
14511
- function valueAt(values, k, size, cubic, part = 1) {
14512
- const at = cubic ? (k * 3 + part) * size : k * size;
14513
- return Array.from(values.subarray(at, at + size));
14514
- }
14515
- function sampleChannel(track, values, size, time$2) {
14516
- const { times } = track;
14517
- const cubic = track.interpolation === "cubic";
14518
- const last = times.length - 1;
14519
- if (time$2 <= (times[0] ?? 0)) return valueAt(values, 0, size, cubic);
14520
- if (time$2 >= (times[last] ?? 0)) return valueAt(values, last, size, cubic);
14521
- const k = keyBefore(times, time$2);
14522
- if (track.interpolation === "step") return valueAt(values, k, size, cubic);
14523
- const t0 = times[k] ?? 0;
14524
- const dt = (times[k + 1] ?? t0) - t0;
14525
- const u = dt > 0 ? (time$2 - t0) / dt : 0;
14526
- const a = valueAt(values, k, size, cubic);
14527
- const b = valueAt(values, k + 1, size, cubic);
14528
- if (cubic) {
14529
- const outA = valueAt(values, k, size, true, 2);
14530
- const inB = valueAt(values, k + 1, size, true, 0);
14531
- const u2 = u * u;
14532
- const u3 = u2 * u;
14533
- const out = a.map((p0, i) => (2 * u3 - 3 * u2 + 1) * p0 + (u3 - 2 * u2 + u) * dt * (outA[i] ?? 0) + (-2 * u3 + 3 * u2) * (b[i] ?? 0) + (u3 - u2) * dt * (inB[i] ?? 0));
14534
- return size === 4 ? normalizeQuat(out) : out;
14535
- }
14536
- if (size === 4) return slerp(a, b, u);
14537
- return a.map((v, i) => v + ((b[i] ?? v) - v) * u);
14538
- }
14539
- /** Each node's local matrix with the clip's tracks applied at `time` (seconds). */
14540
- function localMatrices(nodes, clip, time$2) {
14541
- const t = nodes.map((n) => [...n.translation]);
14542
- const r = nodes.map((n) => [...n.rotation]);
14543
- const s = nodes.map((n) => [...n.scale]);
14544
- const byName = new Map(nodes.map((n, i) => [n.name, i]));
14545
- for (const track of clip?.tracks ?? []) {
14546
- const i = track.node ?? byName.get(track.nodeName);
14547
- if (i === void 0 || !track.times.length) continue;
14548
- if (track.translations) t[i] = sampleChannel(track, track.translations, 3, time$2);
14549
- if (track.rotations) r[i] = sampleChannel(track, track.rotations, 4, time$2);
14550
- if (track.scales) s[i] = sampleChannel(track, track.scales, 3, time$2);
14551
- }
14552
- return nodes.map((n, i) => n.matrix ?? trsMatrix(t[i], r[i], s[i]));
14553
- }
14554
- /**
14555
- * Model-space matrix of every node, posed by `clip` at `time` seconds (the
14556
- * rest pose without a clip). Parents may follow their children in the list.
14557
- */
14558
- function poseNodes(model, clip, time$2) {
14559
- const nodes = model.nodes ?? [];
14560
- const local = localMatrices(nodes, clip, time$2);
14561
- const world = new Array(nodes.length);
14562
- const resolve$1$1 = (i) => {
14563
- const done = world[i];
14564
- if (done) return done;
14565
- const parent = nodes[i].parent;
14566
- const m = parent < 0 ? local[i] : Matrix4Math.multiply(resolve$1$1(parent), local[i]);
14567
- world[i] = m;
14568
- return m;
14569
- };
14570
- return nodes.map((_, i) => resolve$1$1(i));
14571
- }
14572
- /** The joint matrices of one skin for a posed scene graph, padded with identity to MAX_JOINTS. */
14573
- function jointMatrices(model, skinIndex, world) {
14574
- const skin = model.skins?.[skinIndex];
14575
- if (!skin) throw new Error(`model has no skin ${skinIndex}`);
14576
- const out = new Float32Array(MAX_JOINTS * 16);
14577
- for (let j = 0; j < MAX_JOINTS; j++) {
14578
- const node = skin.joints[j];
14579
- if (node === void 0) {
14580
- out.set(Matrix4Math.identity(), j * 16);
14581
- continue;
14582
- }
14583
- const ibm = Array.from(skin.inverseBindMatrices.subarray(j * 16, j * 16 + 16));
14584
- out.set(Matrix4Math.multiply(world[node], ibm), j * 16);
14585
- }
14586
- return out;
14587
- }
14588
14870
  const COMPONENTS = {
14589
14871
  SCALAR: 1,
14590
14872
  VEC2: 2,
@@ -14613,6 +14895,11 @@ function componentReader(componentType, normalized = false) {
14613
14895
  default: return (dv, at) => dv.getFloat32(at, true);
14614
14896
  }
14615
14897
  }
14898
+ /** Mesh compression the loader cannot decode: a file that requires it fails with a clear error. */
14899
+ const UNSUPPORTED_REQUIRED_EXTENSIONS = {
14900
+ KHR_draco_mesh_compression: "Draco mesh compression",
14901
+ EXT_meshopt_compression: "meshopt compression"
14902
+ };
14616
14903
  const GLB_MAGIC = 1179937895;
14617
14904
  const CHUNK_TYPE_JSON = 1313821514;
14618
14905
  const CHUNK_TYPE_BIN = 5130562;
@@ -14667,6 +14954,10 @@ var GltfLoader = class GltfLoader$1 {
14667
14954
  }
14668
14955
  }
14669
14956
  }
14957
+ for (const ext of doc.extensionsRequired ?? []) {
14958
+ const what = UNSUPPORTED_REQUIRED_EXTENSIONS[ext];
14959
+ if (what) throw new Error(`glTF file requires ${ext} (${what}), which framefields cannot decode. Re-export the model without mesh compression (in Blender's glTF exporter, turn off Compression).`);
14960
+ }
14670
14961
  return GltfLoader$1.buildModel(doc, binaryBuffers, options, basePath);
14671
14962
  }
14672
14963
  static unpackGlb(bytes) {
@@ -14754,11 +15045,59 @@ var GltfLoader = class GltfLoader$1 {
14754
15045
  }
14755
15046
  if (!basePath) return void 0;
14756
15047
  const file = path.isAbsolute(image.uri) ? image.uri : path.join(basePath, decodeURIComponent(image.uri));
14757
- if (!fs.existsSync(file)) return void 0;
14758
- const ext = path.extname(file).toLowerCase();
15048
+ if (!fs.existsSync(file)) {
15049
+ GltfLoader$1.warn(`Image "${image.uri}" was not found next to the model; textures using it are skipped.`);
15050
+ return;
15051
+ }
14759
15052
  return {
14760
15053
  bytes: new Uint8Array(fs.readFileSync(file)),
14761
- mime: image.mimeType ?? (ext === ".png" ? "image/png" : "image/jpeg")
15054
+ mime: image.mimeType ?? textureMime(file)
15055
+ };
15056
+ }
15057
+ static warn(message) {
15058
+ console.warn(`[GltfLoader] ${message}`);
15059
+ }
15060
+ /** The UV set of a texture reference (KHR_texture_transform may override it). */
15061
+ static texCoordOf(info) {
15062
+ return info.extensions?.KHR_texture_transform?.texCoord ?? info.texCoord ?? 0;
15063
+ }
15064
+ /** The UV set and transform of the material's base color map (else its first map). */
15065
+ static uvPlan(mat) {
15066
+ const first = [
15067
+ mat?.pbrMetallicRoughness?.baseColorTexture,
15068
+ mat?.normalTexture,
15069
+ mat?.pbrMetallicRoughness?.metallicRoughnessTexture,
15070
+ mat?.occlusionTexture,
15071
+ mat?.emissiveTexture
15072
+ ].find((t) => t !== void 0);
15073
+ return first ? {
15074
+ texCoord: GltfLoader$1.texCoordOf(first),
15075
+ transform: first.extensions?.KHR_texture_transform
15076
+ } : { texCoord: 0 };
15077
+ }
15078
+ /**
15079
+ * A texture's decodable image: EXT_texture_webp's source when present.
15080
+ * KTX2 / Basis (KHR_texture_basisu) and other formats the renderer
15081
+ * cannot decode are skipped with a warning.
15082
+ */
15083
+ static textureImage(doc, binaryBuffers, textureIndex, basePath, label) {
15084
+ const texture = doc.textures?.[textureIndex];
15085
+ if (!texture) return void 0;
15086
+ const source = texture.extensions?.EXT_texture_webp?.source ?? texture.source;
15087
+ if (source === void 0) {
15088
+ if (texture.extensions?.KHR_texture_basisu) GltfLoader$1.warn(`${label} is a KTX2 / Basis texture, which framefields cannot decode; it is skipped (use PNG, JPEG or WebP).`);
15089
+ return;
15090
+ }
15091
+ const image = GltfLoader$1.imageBytes(doc, binaryBuffers, source, basePath);
15092
+ if (!image) return void 0;
15093
+ if (!SUPPORTED_IMAGE_MIMES.has(image.mime)) {
15094
+ GltfLoader$1.warn(`${label} is ${image.mime}, which framefields cannot decode; it is skipped (use PNG, JPEG or WebP).`);
15095
+ return;
15096
+ }
15097
+ const sampler = texture.sampler !== void 0 ? doc.samplers?.[texture.sampler] : void 0;
15098
+ return {
15099
+ ...image,
15100
+ sampler
14762
15101
  };
14763
15102
  }
14764
15103
  static buildMaterial(doc, binaryBuffers, mat, name, basePath) {
@@ -14798,38 +15137,45 @@ var GltfLoader = class GltfLoader$1 {
14798
15137
  1
14799
15138
  ];
14800
15139
  } else if (mat.extensions?.KHR_materials_unlit) material.shading = "unlit";
14801
- const texRef = pbr?.baseColorTexture;
14802
- const texture = texRef ? doc.textures?.[texRef.index] : void 0;
14803
- if (texture?.source !== void 0) {
14804
- const image = GltfLoader$1.imageBytes(doc, binaryBuffers, texture.source, basePath);
14805
- if (image) {
14806
- material.diffuseTextureBuffer = image.bytes;
14807
- material.diffuseTextureMime = image.mime;
14808
- const wrap = texture.sampler !== void 0 ? doc.samplers?.[texture.sampler]?.wrapS : void 0;
14809
- material.textureWrap = wrap === 33071 ? "clamp" : wrap === 33648 ? "mirror" : "repeat";
15140
+ const plan = GltfLoader$1.uvPlan(mat);
15141
+ const image = (info, slot) => {
15142
+ if (!info) return void 0;
15143
+ const label = `The ${slot} map of material "${name}"`;
15144
+ const set = GltfLoader$1.texCoordOf(info);
15145
+ if (set !== plan.texCoord) {
15146
+ GltfLoader$1.warn(`${label} uses UV set ${set}, but the mesh draws with UV set ${plan.texCoord}; the map is skipped.`);
15147
+ return;
14810
15148
  }
14811
- }
14812
- const mapBytes = (ref) => {
14813
- const tex = ref ? doc.textures?.[ref.index] : void 0;
14814
- return tex?.source !== void 0 ? GltfLoader$1.imageBytes(doc, binaryBuffers, tex.source, basePath)?.bytes : void 0;
15149
+ const t = info.extensions?.KHR_texture_transform;
15150
+ if (JSON.stringify(t ?? null) !== JSON.stringify(plan.transform ?? null)) GltfLoader$1.warn(`${label} has a different KHR_texture_transform than the mesh's first map; it is drawn with that one's.`);
15151
+ return GltfLoader$1.textureImage(doc, binaryBuffers, info.index, basePath, label);
14815
15152
  };
14816
- const normal = mapBytes(mat.normalTexture);
15153
+ const base = image(pbr?.baseColorTexture, "base color");
15154
+ if (base) {
15155
+ material.diffuseTextureBuffer = base.bytes;
15156
+ material.diffuseTextureMime = base.mime;
15157
+ const wrap = base.sampler?.wrapS;
15158
+ material.textureWrap = wrap === 33071 ? "clamp" : wrap === 33648 ? "mirror" : "repeat";
15159
+ if (base.sampler?.magFilter === 9728) material.textureFilter = "nearest";
15160
+ }
15161
+ const mapBytes = (info, slot) => image(info, slot)?.bytes;
15162
+ const normal = mapBytes(mat.normalTexture, "normal");
14817
15163
  if (normal) {
14818
15164
  material.normalTextureBuffer = normal;
14819
15165
  material.normalScale = mat.normalTexture?.scale ?? 1;
14820
15166
  }
14821
- const mr = mapBytes(pbr?.metallicRoughnessTexture);
15167
+ const mr = mapBytes(pbr?.metallicRoughnessTexture, "metallic-roughness");
14822
15168
  if (mr) {
14823
15169
  material.metallicRoughnessTextureBuffer = mr;
14824
15170
  material.roughness = pbr?.roughnessFactor ?? 1;
14825
15171
  material.metallic = pbr?.metallicFactor ?? 1;
14826
15172
  }
14827
- const occlusion = mapBytes(mat.occlusionTexture);
15173
+ const occlusion = mapBytes(mat.occlusionTexture, "occlusion");
14828
15174
  if (occlusion) {
14829
15175
  material.occlusionTextureBuffer = occlusion;
14830
15176
  material.occlusionStrength = mat.occlusionTexture?.strength ?? 1;
14831
15177
  }
14832
- const emissiveMap = material.emissive && mapBytes(mat.emissiveTexture);
15178
+ const emissiveMap = material.emissive && mapBytes(mat.emissiveTexture, "emissive");
14833
15179
  if (emissiveMap) material.emissiveTextureBuffer = emissiveMap;
14834
15180
  return material;
14835
15181
  }
@@ -14903,6 +15249,7 @@ var GltfLoader = class GltfLoader$1 {
14903
15249
  const key = materialKeys[idx];
14904
15250
  materials[key] = GltfLoader$1.buildMaterial(doc, binaryBuffers, mat, key, basePath);
14905
15251
  });
15252
+ const uvPlans = (doc.materials ?? []).map((mat) => GltfLoader$1.uvPlan(mat));
14906
15253
  const nodes = GltfLoader$1.buildNodes(doc);
14907
15254
  const skins = (doc.skins ?? []).map((skin) => ({
14908
15255
  joints: skin.joints,
@@ -14966,11 +15313,18 @@ var GltfLoader = class GltfLoader$1 {
14966
15313
  const read = (acc) => acc === void 0 ? void 0 : GltfLoader$1.readFloats(doc, binaryBuffers, acc);
14967
15314
  const pos = read(prim.attributes.POSITION);
14968
15315
  const numVerts = pos.count;
15316
+ const plan = prim.material !== void 0 ? uvPlans[prim.material] : void 0;
15317
+ const attributes = prim.attributes;
15318
+ const uvs = read(attributes[`TEXCOORD_${plan?.texCoord ?? 0}`])?.data ?? read(prim.attributes.TEXCOORD_0)?.data ?? new Float32Array(numVerts * 2);
15319
+ if (plan?.transform) applyTextureTransform(uvs, plan.transform);
14969
15320
  const colors = read(prim.attributes.COLOR_0);
14970
15321
  let rgba;
14971
15322
  if (colors) {
14972
15323
  rgba = new Float32Array(numVerts * 4).fill(1);
14973
- for (let v = 0; v < numVerts; v++) for (let c = 0; c < colors.itemSize; c++) rgba[v * 4 + c] = colors.data[v * colors.itemSize + c] ?? 1;
15324
+ for (let v = 0; v < numVerts; v++) for (let c = 0; c < colors.itemSize; c++) {
15325
+ const value = colors.data[v * colors.itemSize + c] ?? 1;
15326
+ rgba[v * 4 + c] = c < 3 ? linearToSrgb(value) : value;
15327
+ }
14974
15328
  }
14975
15329
  let indices;
14976
15330
  if (prim.indices !== void 0) indices = GltfLoader$1.readIndices(doc, binaryBuffers, prim.indices);
@@ -14998,7 +15352,7 @@ var GltfLoader = class GltfLoader$1 {
14998
15352
  name: meshDef.name ?? `Mesh_${inst.mesh}`,
14999
15353
  positions: pos.data,
15000
15354
  normals: read(prim.attributes.NORMAL)?.data ?? new Float32Array(numVerts * 3),
15001
- uvs: read(prim.attributes.TEXCOORD_0)?.data ?? new Float32Array(numVerts * 2),
15355
+ uvs,
15002
15356
  colors: rgba,
15003
15357
  jointIndices: skinned ? read(prim.attributes.JOINTS_0)?.data : void 0,
15004
15358
  jointWeights: skinned ? read(prim.attributes.WEIGHTS_0)?.data : void 0,
@@ -15072,8 +15426,8 @@ var GltfLoader = class GltfLoader$1 {
15072
15426
  */
15073
15427
  static load(srcOrData, options = {}) {
15074
15428
  if (typeof srcOrData !== "string") return GltfLoader$1.parse(srcOrData, options);
15075
- if (srcOrData.startsWith("/") || srcOrData.startsWith("./") || srcOrData.startsWith("../") || srcOrData.startsWith("file://")) {
15076
- const cleanPath = srcOrData.startsWith("file://") ? srcOrData.replace("file://", "") : srcOrData;
15429
+ const cleanPath = srcOrData.startsWith("file://") ? srcOrData.replace("file://", "") : srcOrData;
15430
+ if (srcOrData.startsWith("/") || srcOrData.startsWith("./") || srcOrData.startsWith("../") || srcOrData.startsWith("file://") || !srcOrData.trimStart().startsWith("{") && fs.existsSync(cleanPath)) {
15077
15431
  const buf = fs.readFileSync(cleanPath);
15078
15432
  const basePath = path.dirname(cleanPath);
15079
15433
  return GltfLoader$1.parse(buf, options, basePath);
@@ -15087,6 +15441,33 @@ function loadGLTF(srcOrData, options = {}) {
15087
15441
  function loadGLB(srcOrData, options = {}) {
15088
15442
  return GltfLoader.load(srcOrData, options);
15089
15443
  }
15444
+ function linearToSrgb(c) {
15445
+ const x = Math.min(Math.max(c, 0), 1);
15446
+ return x <= .0031308 ? x * 12.92 : 1.055 * x ** (1 / 2.4) - .055;
15447
+ }
15448
+ /** The image types the renderer decodes (sharp). */
15449
+ const SUPPORTED_IMAGE_MIMES = new Set([
15450
+ "image/png",
15451
+ "image/jpeg",
15452
+ "image/webp",
15453
+ "image/gif",
15454
+ "image/tiff",
15455
+ "image/avif"
15456
+ ]);
15457
+ /**
15458
+ * Bakes KHR_texture_transform into UVs in place: uv' = T * R * S * uv
15459
+ * (offset, then rotation counter-clockwise in UV space, then scale).
15460
+ */
15461
+ function applyTextureTransform(uvs, { offset = [0, 0], rotation = 0, scale = [1, 1] }) {
15462
+ const c = Math.cos(rotation);
15463
+ const s = Math.sin(rotation);
15464
+ for (let i = 0; i < uvs.length; i += 2) {
15465
+ const u = uvs[i] * scale[0];
15466
+ const v = uvs[i + 1] * scale[1];
15467
+ uvs[i] = c * u + s * v + offset[0];
15468
+ uvs[i + 1] = -s * u + c * v + offset[1];
15469
+ }
15470
+ }
15090
15471
  const DEFAULT_PALETTE = [[
15091
15472
  0,
15092
15473
  0,
@@ -15660,7 +16041,12 @@ var OffLoader = class OffLoader$1 {
15660
16041
  const indexArray = createIndexArray(rawIndices, positions.length / 3);
15661
16042
  const defaultMaterial = {
15662
16043
  name: "off_default",
15663
- diffuseColor: [
16044
+ diffuseColor: hasColors ? [
16045
+ 1,
16046
+ 1,
16047
+ 1,
16048
+ 1
16049
+ ] : [
15664
16050
  .8,
15665
16051
  .8,
15666
16052
  .85,
@@ -26595,6 +26981,7 @@ struct VertexInput {
26595
26981
  @location(2) normal : vec3<f32>,
26596
26982
  @location(3) joints : vec4<f32>,
26597
26983
  @location(4) weights : vec4<f32>,
26984
+ @location(5) color : vec4<f32>, // vertex color (display / sRGB), white when absent
26598
26985
  };
26599
26986
 
26600
26987
  struct VertexOutput {
@@ -26603,6 +26990,7 @@ struct VertexOutput {
26603
26990
  @location(1) worldPos : vec3<f32>,
26604
26991
  @location(2) worldNormal : vec3<f32>,
26605
26992
  @location(3) linearDepth : f32,
26993
+ @location(4) color : vec4<f32>,
26606
26994
  };
26607
26995
 
26608
26996
  @vertex
@@ -26647,6 +27035,7 @@ fn vs_main(in: VertexInput) -> VertexOutput {
26647
27035
 
26648
27036
  out.clipPos = cam.viewProjMatrix * worldPos4;
26649
27037
  out.uv = in.uv;
27038
+ out.color = in.color;
26650
27039
 
26651
27040
  out.linearDepth = dot(worldPos4.xyz - cam.cameraPos.xyz, cam.cameraDir.xyz);
26652
27041
  return out;
@@ -26791,12 +27180,12 @@ fn shadeMesh(in: VertexOutput, isFront: bool) -> MeshFragment {
26791
27180
  if (texColor.a < model.shade.a) {
26792
27181
  discard;
26793
27182
  }
26794
- let alpha = texColor.a * model.params.x * model.colorTint.a * matteCoverage(in.clipPos.xy);
27183
+ let alpha = texColor.a * in.color.a * model.params.x * model.colorTint.a * matteCoverage(in.clipPos.xy);
26795
27184
  if (alpha <= 0.001) {
26796
27185
  discard;
26797
27186
  }
26798
27187
 
26799
- let baseRgb = texColor.rgb * model.colorTint.rgb;
27188
+ let baseRgb = texColor.rgb * in.color.rgb * model.colorTint.rgb;
26800
27189
  var out: MeshFragment;
26801
27190
  if (model.materialParams.w > 1.5) {
26802
27191
  var toon = baseRgb;
@@ -27488,6 +27877,11 @@ var Mesh3DPipeline = class {
27488
27877
  /** (0.5, 0.5, 1): an unperturbed tangent-space normal. */
27489
27878
  flatNormalTexture;
27490
27879
  defaultSkinningBuffer;
27880
+ /** White vertex colors for meshes without any, grown to the largest mesh. */
27881
+ whiteColorBuffer = null;
27882
+ whiteColorCapacity = 0;
27883
+ /** Outgrown white buffers: earlier draws this frame may still read them. */
27884
+ retiredColorBuffers = [];
27491
27885
  modelData = new Float32Array(52 + SURFACE_PARAMS_FLOATS);
27492
27886
  constructor(device, format, depthFormat = "depth24plus", cameraLayout, lightsLayout, sampleCount = 1) {
27493
27887
  this.device = device;
@@ -27579,6 +27973,13 @@ var Mesh3DPipeline = class {
27579
27973
  format: "float32x4"
27580
27974
  }
27581
27975
  ]
27976
+ }, {
27977
+ arrayStride: 16,
27978
+ attributes: [{
27979
+ shaderLocation: 5,
27980
+ offset: 0,
27981
+ format: "float32x4"
27982
+ }]
27582
27983
  }]
27583
27984
  };
27584
27985
  const base = {
@@ -27773,6 +28174,7 @@ var Mesh3DPipeline = class {
27773
28174
  pass.setBindGroup(2, textureBindGroup);
27774
28175
  pass.setBindGroup(3, lightsBindGroup);
27775
28176
  pass.setVertexBuffer(0, meshBuffers.vertexBuffer);
28177
+ pass.setVertexBuffer(1, this.colorsFor(meshBuffers));
27776
28178
  pass.setIndexBuffer(meshBuffers.indexBuffer, meshBuffers.indexFormat);
27777
28179
  pass.drawIndexed(meshBuffers.indexCount);
27778
28180
  }
@@ -27782,10 +28184,31 @@ var Mesh3DPipeline = class {
27782
28184
  pass.setBindGroup(0, lightCameraBindGroup);
27783
28185
  pass.setBindGroup(1, this.packModel(data));
27784
28186
  pass.setVertexBuffer(0, meshBuffers.vertexBuffer);
28187
+ pass.setVertexBuffer(1, this.colorsFor(meshBuffers));
27785
28188
  pass.setIndexBuffer(meshBuffers.indexBuffer, meshBuffers.indexFormat);
27786
28189
  pass.drawIndexed(meshBuffers.indexCount);
27787
28190
  }
28191
+ colorsFor(meshBuffers) {
28192
+ if (meshBuffers.colorBuffer) return meshBuffers.colorBuffer;
28193
+ const needed = Math.max(1, Math.ceil(meshBuffers.vertexBuffer.size / 64));
28194
+ if (!this.whiteColorBuffer || this.whiteColorCapacity < needed) {
28195
+ if (this.whiteColorBuffer) this.retiredColorBuffers.push(this.whiteColorBuffer);
28196
+ const capacity = 2 ** Math.ceil(Math.log2(needed));
28197
+ this.whiteColorBuffer = this.device.createBuffer({
28198
+ label: "Mesh3DWhiteVertexColors",
28199
+ size: capacity * 16,
28200
+ usage: GPUBufferUsage.VERTEX,
28201
+ mappedAtCreation: true
28202
+ });
28203
+ new Float32Array(this.whiteColorBuffer.getMappedRange()).fill(1);
28204
+ this.whiteColorBuffer.unmap();
28205
+ this.whiteColorCapacity = capacity;
28206
+ }
28207
+ return this.whiteColorBuffer;
28208
+ }
27788
28209
  destroy() {
28210
+ this.whiteColorBuffer?.destroy();
28211
+ for (const buffer of this.retiredColorBuffers) buffer.destroy();
27789
28212
  this.modelPool.destroy();
27790
28213
  this.skinningPool.destroy();
27791
28214
  this.defaultWhiteTexture.destroy();
@@ -30332,11 +30755,23 @@ var Renderer3D = class {
30332
30755
  if (is32Bit) new Uint32Array(indexBuffer.getMappedRange()).set(mesh.indices);
30333
30756
  else new Uint16Array(indexBuffer.getMappedRange()).set(mesh.indices);
30334
30757
  indexBuffer.unmap();
30758
+ let colorBuffer;
30759
+ if (mesh.colors && mesh.colors.length >= numVerts * 4) {
30760
+ colorBuffer = this.device.createBuffer({
30761
+ label: "Mesh3DVertexColors",
30762
+ size: Math.max(16, numVerts * 16),
30763
+ usage: GPUBufferUsage.VERTEX,
30764
+ mappedAtCreation: true
30765
+ });
30766
+ new Float32Array(colorBuffer.getMappedRange()).set(mesh.colors.subarray(0, numVerts * 4));
30767
+ colorBuffer.unmap();
30768
+ }
30335
30769
  cached$1 = {
30336
30770
  vertexBuffer,
30337
30771
  indexBuffer,
30338
30772
  indexCount: mesh.indices.length,
30339
- indexFormat: is32Bit ? "uint32" : "uint16"
30773
+ indexFormat: is32Bit ? "uint32" : "uint16",
30774
+ colorBuffer
30340
30775
  };
30341
30776
  this.meshBuffersCache.set(mesh, cached$1);
30342
30777
  return cached$1;
@@ -30354,7 +30789,8 @@ var Renderer3D = class {
30354
30789
  }),
30355
30790
  indexBuffer: meshBuffers.indexBuffer,
30356
30791
  indexCount: meshBuffers.indexCount,
30357
- indexFormat: meshBuffers.indexFormat
30792
+ indexFormat: meshBuffers.indexFormat,
30793
+ colorBuffer: meshBuffers.colorBuffer
30358
30794
  };
30359
30795
  }
30360
30796
  drawMesh3D(pass, cameraBindGroup, lightsBindGroup, meshOrBuffers, texture, opts = {}, useMrt = false) {
@@ -30364,13 +30800,15 @@ var Renderer3D = class {
30364
30800
  1,
30365
30801
  1,
30366
30802
  1
30367
- ], twoSided = true, wireframe = false, isSkinned = false, skinningMatrices, material = "lit", shadeColor, alphaCutoff, textureWrap = "clamp", shininess = 32, roughness, specularIntensity = .5, ambientIntensity = 1, metallic } = opts;
30803
+ ], twoSided = true, wireframe = false, isSkinned = false, skinningMatrices, material = "lit", shadeColor, alphaCutoff, textureWrap = "clamp", textureFilter = "linear", shininess = 32, roughness, specularIntensity = .5, ambientIntensity = 1, metallic } = opts;
30368
30804
  const m = this.meshModelMatrix(opts);
30369
30805
  const normalMatrix = opts.normalMatrix ?? Light3D.computeNormalMatrix(m);
30370
30806
  const address = ADDRESS_MODES[textureWrap];
30371
30807
  const sampler = this.samplerCache.getSampler(this.device, {
30372
30808
  addressModeU: address,
30373
- addressModeV: address
30809
+ addressModeV: address,
30810
+ magFilter: textureFilter,
30811
+ ...textureFilter === "nearest" && { maxAnisotropy: 1 }
30374
30812
  });
30375
30813
  const glassy = material === "glass" || material === "acrylic" || material === "frosted";
30376
30814
  const glass = glassy ? {
@@ -30469,7 +30907,14 @@ var Renderer3D = class {
30469
30907
  "sharp"
30470
30908
  )).default;
30471
30909
  const source = Buffer.from(encoded instanceof Uint8Array ? encoded : new Uint8Array(encoded));
30472
- const { width, height } = await sharp(source).metadata();
30910
+ let width;
30911
+ let height;
30912
+ try {
30913
+ ({width, height} = await sharp(source).metadata());
30914
+ } catch (err) {
30915
+ console.warn(`[Renderer3D] Could not decode texture "${label}"; drawing without it:`, err instanceof Error ? err.message : err);
30916
+ return;
30917
+ }
30473
30918
  if (!width || !height) return void 0;
30474
30919
  const mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1;
30475
30920
  const texture = this.device.createTexture({
@@ -31207,4 +31652,4 @@ if (typeof globalThis !== "undefined") globalThis.__GATEWAI_DELAYS__ = globalThi
31207
31652
 
31208
31653
  //#endregion
31209
31654
  export { resolveTextSpans as $, TextSelectorSchema as $t, ensureDevice as A, sampleAudioSpectrumScale as An, GlyphAnchorGroupingSchema as At, loadModel3D as B, SignalRegistry as Bt, drawGifNode as C, evaluateTypewriterState as Cn, AudioSpectrumTypographySchema as Ct, drawSignalNode as D, getScramblePool as Dn, EnhancedTextNodeSchema as Dt, drawParagraphNode as E, generatePathSegments as En, DynamicStrokePropertiesSchema as Et, getRenderer2D as F, ParticleDissolveConfigSchema as Ft, parseColor as G, TextAnimatorBuilder as Gt, lutStore as H, Spatial3DPropertiesSchema as Ht, initHeadlessWebGPU as I, SelectorBasedOnSchema as It, parseOBJ as J, TextPathBuilder as Jt, parseFBX as K, TextCursorConfigSchema as Kt, inputStore as L, SelectorModeSchema as Lt, generateExtrudedTextGeometry as M, samplePathChainGeometry as Mn, KineticMarqueeConfigSchema as Mt, getHeadlessFontPath as N, signalRegistry as Nn, LineSpacingDynamicsSchema as Nt, drawSvgNode as O, getShuffledOrder as On, ExpressionSelectorSchema as Ot, getPrimitiveGeometry as P, waveToCubicBeziers as Pn, LongShadowConfigSchema as Pt, registerHeadlessFont as Q, TextScrambleConfigSchema as Qt, jointMatrices as R, SelectorShapeSchema as Rt, drawCaptionNode as S, evaluateSpringProgress as Sn, AudioSignalComputePipeline as St, drawLottieNode as T, generateArcLengthLUT as Tn, CubicBezierSegmentSchema as Tt, measureMaxCaptionHeight as U, SpringPhysicsSchema as Ut, loadOBJ as V, SkewPropertiesSchema as Vt, mediaDecoderCache as W, TextAnchorConfigSchema as Wt, poseNodes as X, TextPathSourceSchema as Xt, parseSvgPathToSegments as Y, TextPathOptionsSchema as Yt, probeVideoSize as Z, TextRangeSelectorSchema as Zt, buildShapeSvgPath as _, evaluateCubicBezierDerivative as _n, AdvancedTextAnimatorBuilder as _t, GlassPipeline as a, Vec2Schema as an, resolveMediaSourceUrl as at, colorMatrixWgsl as b, evaluateSelectorCombination as bn, AnchorPresetSchema as bt, Renderer2D as c, buildPathLUT as cn, LIGHT_UNIFORM_SIZE_BYTES as ct, ScreenSpaceRelightPipeline as d, computeLineLeadingDisplacement as dn, Matrix4Math as dt, TextSignalSelectorSchema as en, shaderStore as et, ShadowPipeline as f, computeRangeSelectorWeight as fn, Path3D as ft, WebGPUAudioProcessor as g, evaluateCubicBezier as gn, AETextAnimatorSchema as gt, SlugPipeline as h, ellipseToCubicBeziers as hn, resolveEnvironment as ht, EffectPipeline as i, VariableFontAxesSchema as in, GetFontAssetUrl as it, fitSpansToText as j, sampleCurveGeometry as jn, GlyphAnchorPointSchema as jt, drawVideoNode as k, parseSvgPathToCubicBeziers as kn, ExtendedGlyphPropertiesSchema as kt, Renderer3D as l, buildWGSLSignalFn as ln, Light3D as lt, SlugGeometry as m, distanceToCurveParameter as mn, parseColorToRgb as mt, AudioLatentTrackCache as n, TypewriterAnimatorSchema as nn, srtLoader as nt, MotionBlurPipeline as o, VolumetricFormationConfigSchema as on, Camera3D as ot, SlugFontCache as p, deterministicWigglyNoise as pn, Vector3Math as pt, parseMTL as q, TextCursorStyleSchema as qt, AudioMeshDeformPipeline as r, TypingCadenceSchema as rn, textureCache as rt, NodeSurfaceProvider as s, VolumetricFormationModeSchema as sn, ENVIRONMENT_PRESETS as st, AudioLatentEngine as t, TextWigglySelectorSchema as tn, spansText as tt, SSAOPipeline as u, computeAnchorPivotOffset as un, MAX_LIGHTS as ut, clearAllVideoCache as v, evaluateExpressionSelector as vn, AdvancedTextAnimatorSchema as vt, drawImageNode as w, evaluateVolumetricFormation as wn, ChromaticGlitchConfigSchema as wt, describeGpu as x, evaluateSkewMatrix as xn, AnimatableGlyphPropsSchema as xt, clearHeadlessFonts as y, evaluateMarqueeOffset as yn, AnchorGroupingSchema as yt, loadFBX as z, SelectorUnitsSchema as zt };
31210
- //# sourceMappingURL=dist-CturBK2Z.mjs.map
31655
+ //# sourceMappingURL=dist-DRB6Vg5u.mjs.map