@woosh/meep-engine 3.24.0 → 3.26.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 (349) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +59 -0
  3. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  4. package/src/core/geom/3d/shape/HeightMapShape3D.js +285 -0
  5. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  6. package/src/core/geom/3d/shape/PosedShape3D.js +57 -0
  7. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  8. package/src/core/geom/3d/shape/TransformedShape3D.js +102 -0
  9. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  10. package/src/core/geom/3d/shape/UnionShape3D.js +64 -0
  11. package/src/engine/graphics3/PickingSystem.d.ts +3 -3
  12. package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
  13. package/src/engine/graphics3/PickingSystem.js +52 -55
  14. package/src/engine/graphics3/shader_picking_gather.d.ts +40 -0
  15. package/src/engine/graphics3/shader_picking_gather.d.ts.map +1 -0
  16. package/src/engine/graphics3/shader_picking_gather.js +120 -0
  17. package/src/engine/physics/cloth/MEASUREMENTS.md +1632 -0
  18. package/src/engine/physics/cloth/PLAN.md +738 -95
  19. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +84 -17
  20. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  21. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +451 -42
  22. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts +52 -0
  23. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts.map +1 -0
  24. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.js +70 -0
  25. package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts +12 -3
  26. package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
  27. package/src/engine/physics/cloth/ecs/ClothDynamics.js +340 -331
  28. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +101 -29
  29. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  30. package/src/engine/physics/cloth/ecs/ClothInstance.js +110 -31
  31. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts +84 -0
  32. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts.map +1 -0
  33. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.js +141 -0
  34. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +71 -81
  35. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  36. package/src/engine/physics/cloth/ecs/ClothSystem.js +135 -856
  37. package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts +48 -0
  38. package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts.map +1 -0
  39. package/src/engine/physics/cloth/ecs/ClothWorkerCore.js +107 -0
  40. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +323 -0
  41. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -0
  42. package/src/engine/physics/cloth/ecs/ClothWorld.js +1248 -0
  43. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +192 -0
  44. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -0
  45. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +578 -0
  46. package/src/engine/physics/cloth/ecs/cloth.worker.d.ts +2 -0
  47. package/src/engine/physics/cloth/ecs/cloth.worker.d.ts.map +1 -0
  48. package/src/engine/physics/cloth/ecs/cloth.worker.js +12 -0
  49. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  50. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +2 -1
  51. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +85 -3
  52. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  53. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +366 -271
  54. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +95 -12
  55. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  56. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +170 -27
  57. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +19 -0
  58. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  59. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +249 -201
  60. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +21 -0
  61. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  62. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +62 -1
  63. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts +29 -0
  64. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts.map +1 -0
  65. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.js +78 -0
  66. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  67. package/src/engine/physics/cloth/ecs/cloth_write_back.js +18 -10
  68. package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts +17 -0
  69. package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts.map +1 -0
  70. package/src/engine/physics/cloth/ecs/makeClothWorker.js +18 -0
  71. package/src/engine/physics/cloth/playground/README.md +73 -2
  72. package/src/engine/physics/cloth/playground/collide_main.js +1 -1
  73. package/src/engine/physics/cloth/playground/wind.html +166 -0
  74. package/src/engine/physics/cloth/playground/wind_build.d.ts +81 -0
  75. package/src/engine/physics/cloth/playground/wind_build.d.ts.map +1 -0
  76. package/src/engine/physics/cloth/playground/wind_build.js +141 -0
  77. package/src/engine/physics/cloth/playground/wind_main.d.ts +2 -0
  78. package/src/engine/physics/cloth/playground/wind_main.d.ts.map +1 -0
  79. package/src/engine/physics/cloth/playground/wind_main.js +681 -0
  80. package/src/engine/physics/cloth/solver/ClothState.d.ts +134 -79
  81. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  82. package/src/engine/physics/cloth/solver/ClothState.js +273 -12
  83. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +29 -8
  84. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
  85. package/src/engine/physics/cloth/solver/cloth_contact_find.js +37 -9
  86. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +45 -2
  87. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  88. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +46 -2
  89. package/src/engine/physics/cloth/solver/cloth_step.d.ts +21 -2
  90. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  91. package/src/engine/physics/cloth/solver/cloth_step.js +173 -4
  92. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +40 -0
  93. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -0
  94. package/src/engine/physics/cloth/solver/cloth_step_instance.js +284 -0
  95. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts +107 -0
  96. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts.map +1 -0
  97. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.js +263 -0
  98. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +9 -2
  99. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -1
  100. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +9 -2
  101. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +102 -0
  102. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -0
  103. package/src/engine/physics/cloth/wind/AbstractClothWind.js +114 -0
  104. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +87 -0
  105. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -0
  106. package/src/engine/physics/cloth/wind/ClothAmbientWind.js +185 -0
  107. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +79 -0
  108. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -0
  109. package/src/engine/physics/cloth/wind/ClothFluidWind.js +213 -0
  110. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +106 -0
  111. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -0
  112. package/src/engine/physics/cloth/wind/cloth_sample_air.js +297 -0
  113. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  114. package/src/shade/device/mock/texture_copy.d.ts.map +1 -1
  115. package/src/shade/device/mock/texture_copy.js +28 -0
  116. package/src/shade/playground/vgeo_runtime/README.md +94 -0
  117. package/src/shade/playground/vgeo_runtime/index.html +62 -0
  118. package/src/shade/playground/vgeo_runtime/main.d.ts +2 -0
  119. package/src/shade/playground/vgeo_runtime/main.d.ts.map +1 -0
  120. package/src/shade/playground/vgeo_runtime/main.js +399 -0
  121. package/src/shade/playground/vgeo_viewer/sample_asset.js +1 -1
  122. package/src/shade/renderer/camera/Camera.d.ts +14 -0
  123. package/src/shade/renderer/camera/Camera.d.ts.map +1 -1
  124. package/src/shade/renderer/camera/Camera.js +7 -1
  125. package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.d.ts.map +1 -1
  126. package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.js +16 -1
  127. package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
  128. package/src/shade/renderer/geometry/GPUGeometryManager.js +29 -9
  129. package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts +10 -0
  130. package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts.map +1 -1
  131. package/src/shade/renderer/geometry/GPUGeometryMetadata.js +13 -0
  132. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +46 -18
  133. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +70 -22
  134. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +97 -19
  135. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +12 -2
  136. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  137. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +14 -3
  138. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +2 -2
  139. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
  140. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +124 -6
  141. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +22 -0
  142. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +1 -0
  143. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.js +53 -0
  144. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +8 -1
  145. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  146. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +8 -1
  147. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts +16 -0
  148. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts.map +1 -0
  149. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.js +15 -0
  150. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts +54 -0
  151. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts.map +1 -0
  152. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.js +53 -0
  153. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +13 -2
  154. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
  155. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +14 -2
  156. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +100 -0
  157. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  158. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +113 -0
  159. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
  160. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +5 -0
  161. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  162. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +108 -0
  163. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  164. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +23 -0
  165. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  166. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +40 -0
  167. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +228 -0
  168. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -0
  169. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +932 -0
  170. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +176 -0
  171. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -0
  172. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +425 -0
  173. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +241 -0
  174. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +574 -0
  175. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +120 -0
  176. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -0
  177. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +674 -0
  178. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +287 -0
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -0
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +976 -0
  181. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +42 -0
  182. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -0
  183. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +96 -0
  184. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts +16 -0
  185. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts.map +1 -0
  186. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.js +15 -0
  187. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts +19 -0
  188. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -0
  189. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +83 -0
  190. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts +9 -0
  191. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts.map +1 -0
  192. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.js +8 -0
  193. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +19 -0
  194. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -0
  195. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +160 -0
  196. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts +3 -0
  197. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -0
  198. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +255 -0
  199. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts +3 -0
  200. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -0
  201. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +80 -0
  202. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts +3 -0
  203. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -0
  204. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +144 -0
  205. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts +3 -0
  206. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -0
  207. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +530 -0
  208. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +39 -0
  209. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -0
  210. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +54 -0
  211. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts +41 -0
  212. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts.map +1 -0
  213. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.js +56 -0
  214. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts +49 -0
  215. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -0
  216. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +328 -0
  217. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +10 -0
  218. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts.map +1 -0
  219. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +51 -0
  220. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +13 -0
  221. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -0
  222. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +12 -0
  223. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +17 -0
  224. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts.map +1 -0
  225. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +99 -0
  226. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts +9 -0
  227. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts.map +1 -0
  228. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +8 -0
  229. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +7 -0
  230. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts.map +1 -0
  231. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +27 -0
  232. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts +14 -0
  233. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts.map +1 -0
  234. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js +13 -0
  235. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts +7 -0
  236. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts.map +1 -0
  237. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.js +35 -0
  238. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts +9 -0
  239. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts.map +1 -0
  240. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.js +8 -0
  241. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts +15 -0
  242. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts.map +1 -0
  243. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.js +14 -0
  244. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +7 -0
  245. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts.map +1 -0
  246. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +36 -0
  247. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +10 -0
  248. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -0
  249. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +9 -0
  250. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts +11 -0
  251. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts.map +1 -0
  252. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.js +54 -0
  253. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts +9 -0
  254. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts.map +1 -0
  255. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.js +8 -0
  256. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts +11 -0
  257. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts.map +1 -0
  258. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.js +10 -0
  259. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts +7 -0
  260. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts.map +1 -0
  261. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.js +30 -0
  262. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts +9 -0
  263. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts.map +1 -0
  264. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.js +8 -0
  265. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts +13 -0
  266. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts.map +1 -0
  267. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.js +12 -0
  268. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts +14 -0
  269. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts.map +1 -0
  270. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.js +13 -0
  271. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +11 -0
  272. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts.map +1 -0
  273. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +61 -0
  274. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts +9 -0
  275. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -0
  276. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +8 -0
  277. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts +13 -0
  278. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts.map +1 -0
  279. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.js +12 -0
  280. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts +9 -0
  281. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts.map +1 -0
  282. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.js +8 -0
  283. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts +20 -0
  284. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts.map +1 -0
  285. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.js +19 -0
  286. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts +23 -0
  287. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -0
  288. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +70 -0
  289. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +18 -0
  290. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -0
  291. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +25 -0
  292. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +35 -0
  293. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -0
  294. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +41 -0
  295. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +32 -0
  296. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -0
  297. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +138 -0
  298. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  299. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  300. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  301. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +9 -0
  302. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  303. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +12 -1
  304. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  305. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +12 -1
  306. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  307. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +10 -0
  308. package/src/shade/renderer/scene/GPUSceneContext.d.ts +18 -0
  309. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  310. package/src/shade/renderer/scene/GPUSceneContext.js +647 -594
  311. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
  312. package/src/shade/renderer/scene/rows/GPUSceneRows.js +22 -0
  313. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts +35 -0
  314. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts.map +1 -0
  315. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.js +91 -0
  316. package/src/engine/graphics3/instantiate_scene_bundle.d.ts +0 -54
  317. package/src/engine/graphics3/instantiate_scene_bundle.d.ts.map +0 -1
  318. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +0 -39
  319. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts.map +0 -1
  320. package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts +0 -35
  321. package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts.map +0 -1
  322. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts +0 -96
  323. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts.map +0 -1
  324. package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts +0 -20
  325. package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts.map +0 -1
  326. package/src/shade/renderer/animation/skin_bind_transform.d.ts +0 -25
  327. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +0 -1
  328. package/src/shade/renderer/animation/skin_frame_defect.d.ts +0 -34
  329. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +0 -1
  330. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +0 -24
  331. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +0 -1
  332. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +0 -70
  333. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +0 -1
  334. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +0 -96
  335. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +0 -1
  336. package/src/shade/renderer/scene/InstanceBatch.d.ts +0 -92
  337. package/src/shade/renderer/scene/InstanceBatch.d.ts.map +0 -1
  338. package/src/shade/renderer/scene/Mesh.d.ts +0 -55
  339. package/src/shade/renderer/scene/Mesh.d.ts.map +0 -1
  340. package/src/shade/renderer/scene/Node3D.d.ts +0 -159
  341. package/src/shade/renderer/scene/Node3D.d.ts.map +0 -1
  342. package/src/shade/renderer/scene/Node3DDirtyList.d.ts +0 -59
  343. package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +0 -1
  344. package/src/shade/renderer/scene/SceneNodeBinding.d.ts +0 -52
  345. package/src/shade/renderer/scene/SceneNodeBinding.d.ts.map +0 -1
  346. package/src/shade/renderer/scene/SkinnedMesh.d.ts +0 -27
  347. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +0 -1
  348. package/src/shade/renderer/scene/TransformAuthority.d.ts +0 -19
  349. package/src/shade/renderer/scene/TransformAuthority.d.ts.map +0 -1
@@ -0,0 +1,1248 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { quat3_multiply } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
3
+ import { quat3_nlerp } from "../../../../core/geom/3d/quaternion/quat3_nlerp.js";
4
+ import { v3_quaternion_apply } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
5
+ import { v3_quaternion_apply_inverse } from "../../../../core/geom/vec3/v3_quaternion_apply_inverse.js";
6
+ import { EntityObserver } from "../../../ecs/EntityObserver.js";
7
+ import {
8
+ TransformAttachment
9
+ } from "../../../ecs/transform-attachment/TransformAttachment.js";
10
+ import {
11
+ TransformAttachmentSystem
12
+ } from "../../../ecs/transform-attachment/TransformAttachmentSystem.js";
13
+ import {
14
+ TRANSFORM64_ROTATION_OFFSET,
15
+ TRANSFORM64_TRANSLATION_OFFSET
16
+ } from "../../../ecs/transform/Transform64.js";
17
+ import { t64_evaluate_world } from "../../../ecs/transform/t64_evaluate_world.js";
18
+ import {
19
+ collect_entity_playbacks
20
+ } from "../../../graphics3/pose/collect_entity_playbacks.js";
21
+ import { MeshSystem } from "../../../graphics3/MeshSystem.js";
22
+ import { CLOTH_SELF_RADIUS_MAX_RATIO, CLOTH_SELF_RADIUS_MIN_RATIO } from "../solver/cloth_solver_constants.js";
23
+ import { cloth_air_changed, cloth_sample_air } from "../wind/cloth_sample_air.js";
24
+ import { ClothStepParam } from "../solver/cloth_step_instance.js";
25
+ import { ClothStepResult } from "../solver/ClothState.js";
26
+ import { ClothColliderSystem } from "./ClothColliderSystem.js";
27
+ import { ClothDynamicsFlags } from "./ClothDynamicsFlags.js";
28
+ import { ClothFlags } from "./ClothFlags.js";
29
+ import { ClothInstance } from "./ClothInstance.js";
30
+ import { ClothRig } from "./ClothRig.js";
31
+ import {
32
+ CLOTH_ANCHOR_ROTATION_LIMIT,
33
+ CLOTH_ANCHOR_TRAVEL_REACH,
34
+ CLOTH_SLEEP_DISPLACEMENT_RATIO,
35
+ CLOTH_SLEEP_SPEED_RATIO,
36
+ CLOTH_SLEEP_STEPS,
37
+ CLOTH_STABILISE_STEPS,
38
+ CLOTH_TELEPORT_FLOOR,
39
+ CLOTH_TELEPORT_REACH,
40
+ cloth_drag_response,
41
+ cloth_velocity_limit,
42
+ cloth_velocity_retention
43
+ } from "./cloth_dynamics_map.js";
44
+ import {
45
+ cloth_colliders_moved,
46
+ cloth_gather_colliders,
47
+ cloth_gather_reach,
48
+ cloth_remember_colliders
49
+ } from "./cloth_gather_colliders.js";
50
+ import {
51
+ cloth_dynamics_change_needs_reseed,
52
+ cloth_reparameterise
53
+ } from "./cloth_build_rows.js";
54
+ import { Cloth } from "./Cloth.js";
55
+ import { cloth_release_subtree, cloth_seed_subtree } from "./cloth_seed_subtree.js";
56
+ import { cloth_release_rig, cloth_seed_rig } from "./cloth_seed_rig.js";
57
+ import { cloth_write_back } from "./cloth_write_back.js";
58
+ import { cloth_write_back_rig } from "./cloth_write_back_rig.js";
59
+
60
+ /**
61
+ * # Every cloth in a scene, and everything a step needs that is not arithmetic
62
+ *
63
+ * One {@link ClothInstance} per {@link Cloth} component, stepped at the fixed
64
+ * rate. Per step, per cloth: move the local frame with the anchor, point the
65
+ * leash rows at where they measure from, gather the colliders that could touch
66
+ * it, step, and put the result back on the subtree. The animated pose the
67
+ * leashes are measured against is not recomposed per step — in the entity path
68
+ * it is constant in the anchor's own frame, and `cloth_seed_subtree.js` bakes
69
+ * it.
70
+ *
71
+ * ## Why this is not a `System`
72
+ *
73
+ * Because two of them drive it. {@link ClothSystem} steps every cloth inline;
74
+ * {@link WorkerClothSystem} hands the arithmetic to a worker and joins it on the
75
+ * next tick. §12 asks those two to produce **bit-identical** results, and the
76
+ * cheapest way to be sure of that is for there to be one copy of everything
77
+ * except the dispatch — so the registry, the seeding, the frame handling, the
78
+ * broadphase and the write-back live here, and the two systems differ only in
79
+ * where {@link ClothWorld#prepare} and {@link ClothWorld#finish} are separated
80
+ * by a thread.
81
+ *
82
+ * That split is also exactly §12's: "arbitrary user-facing work — component
83
+ * reads, `Transform64` sync, index queries — stays on the main thread; the pure
84
+ * numeric step crosses". {@link ensure_seeded} and {@link advance} are the first
85
+ * half, `cloth_step_instance` is the second, {@link finish} is what the result
86
+ * is worth.
87
+ *
88
+ * Seeding is its own call rather than the top of {@link advance} because the
89
+ * worker needs to get between them: a cloth's `ClothState` is what it registers
90
+ * with the worker, so it has to exist — and be acknowledged — before the cloth
91
+ * can be nominated for a step, and a re-seed replaces it and starts that again.
92
+ *
93
+ * ## The parameter block
94
+ *
95
+ * {@link prepare} does not call the step and does not return its arguments; it
96
+ * **writes them into a `Float64` block** the caller supplies. For
97
+ * {@link ClothSystem} that block is a private scratch array and the step follows
98
+ * immediately; for {@link WorkerClothSystem} it is a slice of the shared command
99
+ * region and the step happens on another thread. Neither system ever computes a
100
+ * step parameter of its own, so neither can compute one differently.
101
+ *
102
+ * ## The local frame and the inertia model — §7 P7
103
+ *
104
+ * Particles live in the anchor's own frame. When the anchor moves, the cloth is
105
+ * asked how much of that motion it would like to feel:
106
+ * `ClothDynamics#inertia = 1` keeps every particle's **world** position and
107
+ * velocity, so a cape trails a sprinting character; `0` keeps their **local**
108
+ * ones, so the cloth rides the anchor as though it were standing still. The
109
+ * blend between the two is a lerp in the local frame, which is all the frame
110
+ * change is.
111
+ *
112
+ * The felt motion is limited before the blend, which is what makes "character
113
+ * sprints, cape behaves" a default rather than a tuning exercise, and the limits
114
+ * are derived from the cloth's own reach rather than authored.
115
+ *
116
+ * ## The wind — §10
117
+ *
118
+ * {@link wind} is a source of air and {@link advance} samples it into the
119
+ * cloth's own buffer, once per particle per step. That is the one piece of M6
120
+ * that stays on this thread: a fluid field is an ECS component holding a
121
+ * hundred thousand floats that its own solver rewrites every step, and the
122
+ * *answer* is three floats a particle. `cloth_sample_air.js` carries the
123
+ * decision; the aerodynamic arithmetic built on top of it lives in
124
+ * `cloth_step_instance` with everything else the budget is spent on.
125
+ *
126
+ * A source is optional and additive, like the collider index. With none, the
127
+ * step's drag response is zero, `cloth_step` takes the uniform-gravity path,
128
+ * and a scene produces the bytes M5 measured.
129
+ *
130
+ * ## Teleport — §7 P6
131
+ *
132
+ * Anchor motion past a threshold is not locomotion and is not treated as any
133
+ * amount of it. `ClothFlags.TeleportKeep` carries the shape across, which in a
134
+ * local frame is literally doing nothing; the default re-seeds at the animated
135
+ * pose with zero velocity and opens a stabilisation window in which the
136
+ * velocity limit ramps back from nothing, so the first steps after a reset
137
+ * cannot produce a whip.
138
+ *
139
+ * @author Alex Goldring
140
+ * @copyright Company Named Limited (c) 2026
141
+ */
142
+ export class ClothWorld {
143
+
144
+ /**
145
+ * World gravity, m/s². Rotated into each cloth's local frame per step.
146
+ * @type {Float64Array}
147
+ */
148
+ gravity = new Float64Array([0, -9.81, 0]);
149
+
150
+ /**
151
+ * Live cloths, in the order they were linked.
152
+ *
153
+ * **Not sorted by hierarchy depth**, and it used to be. The sort existed so
154
+ * that a nested cloth advanced against an anchor the cloth above it had
155
+ * already written this step — which `WorkerClothSystem` cannot do, because
156
+ * the step it is writing back happened on another thread a tick ago. Both
157
+ * systems now advance every cloth, then step every cloth, then finish every
158
+ * cloth, so a nested cloth reads its parent one fixed step stale on both
159
+ * sides and the sort has nothing left to decide. `ClothSystem#fixedUpdate`
160
+ * carries the argument; `MEASUREMENTS.md` §56 carries the measurement.
161
+ * @type {ClothInstance[]}
162
+ */
163
+ instances = [];
164
+
165
+ /**
166
+ * Where a skinned model instance comes from. Anything that answers
167
+ * `instance_of(entity)` with a `PrefabInstance` — skins whose joints
168
+ * are entities — will do, and {@link MeshSystem} is what does in a
169
+ * running engine.
170
+ *
171
+ * Public and assignable because a garment's joints are a *rendering*
172
+ * structure and `MeshSystem` needs a graphics engine, a scene and a loader
173
+ * to exist — so a spec that wants joints without a renderer sets this
174
+ * instead, and gets exactly the interface the system uses.
175
+ *
176
+ * `null` means no rig can seed, which is the same additive shape the
177
+ * collider index has: a scene without one gets M1's cloth rather than an
178
+ * error.
179
+ * @type {{instance_of: function(number): (PrefabInstance|null)}|null}
180
+ */
181
+ models = null;
182
+
183
+ /**
184
+ * @type {EntityManager|null}
185
+ */
186
+ entityManager = null;
187
+
188
+ /**
189
+ * @type {TransformAttachmentSystem|null}
190
+ */
191
+ attachments = null;
192
+
193
+ /**
194
+ * The collider index's owner, or `null` when the manager has none — in
195
+ * which case cloth simply does not collide, which is exactly M1's
196
+ * behaviour and is what makes the collision layer additive rather than a
197
+ * new requirement on every scene.
198
+ * @type {ClothColliderSystem|null}
199
+ */
200
+ colliders = null;
201
+
202
+ /**
203
+ * Where the air comes from — §10. `null` means no air, which is the
204
+ * default and is **additive** in exactly the way the collider index is: a
205
+ * scene without one gets M5's cloth rather than an error, and adding one is
206
+ * the whole of turning wind on.
207
+ *
208
+ * Null rather than a still-air ambient, and the distinction is real. A
209
+ * source that answers zero everywhere is not "no wind": the drag row is a
210
+ * relaxation toward the air, so still air still resists a *moving* cloth,
211
+ * with the normal-facing factor making that resistance anisotropic. That is
212
+ * better physics than `ClothDynamics#damping`'s isotropic decay and it is
213
+ * not free — so it is a thing a scene asks for. What a cloth does in air
214
+ * that is not moving is `damping`'s job (§7 P5, where it is load-bearing);
215
+ * what it does in air that *is* is this.
216
+ *
217
+ * Assign {@link ClothAmbientWind} for weather, {@link ClothFluidWind} to
218
+ * read the fluid simulation and fall back to weather outside it.
219
+ * @type {AbstractClothWind|null}
220
+ */
221
+ wind = null;
222
+
223
+ /**
224
+ * Seconds of fixed step this world has **stepped**, which is the clock a
225
+ * wind source's gust is a function of.
226
+ *
227
+ * Accumulated from `dt` rather than read from a wall clock, because
228
+ * {@link ClothSystem} and {@link WorkerClothSystem} share this object and
229
+ * §12's parity gate is over every byte of every step: two runs of the same
230
+ * scene have the same `dt` sequence and therefore the same weather.
231
+ *
232
+ * **Stepped rather than ticked**, and that distinction is load-bearing
233
+ * rather than pedantic. A cloth under the worker is not advanced until its
234
+ * registration is acknowledged (§39), so the two systems reach a cloth's
235
+ * *first* step at different tick counts — and a clock that counted ticks
236
+ * would hand that cloth a different gust on the same step under the two
237
+ * systems, which is a divergence the parity gate would report and the design
238
+ * would deserve. So the clock moves when {@link advance} first runs in a
239
+ * tick: the world's weather is a function of how much cloth time has
240
+ * passed, not of how long something has been asking.
241
+ * @type {number}
242
+ */
243
+ time = 0;
244
+
245
+ /**
246
+ * Fixed-step seconds this tick owes the clock, consumed by the first
247
+ * {@link advance} — see {@link time}. Zero once spent.
248
+ * @type {number}
249
+ * @private
250
+ */
251
+ __wind_pending = 0;
252
+
253
+ /**
254
+ * Whether a cloth's {@link ClothState} must be backed by a
255
+ * `SharedArrayBuffer`. Stamped onto every instance as it is linked;
256
+ * {@link WorkerClothSystem} sets it, {@link ClothSystem} leaves it alone.
257
+ * @type {boolean}
258
+ */
259
+ shared_state = false;
260
+
261
+ /**
262
+ * @type {Map<number, ClothInstance>}
263
+ * @private
264
+ */
265
+ __by_entity = new Map();
266
+
267
+ /**
268
+ * Watches `Cloth` + {@link ClothRig} so the rig path is a **live**
269
+ * discriminator rather than one read once.
270
+ *
271
+ * `ClothSystem#dependencies` is `[Cloth, Transform64]` — the tuple that
272
+ * makes something a cloth — so a `ClothRig` added to a live cloth completes
273
+ * no tuple the system observes and a `ClothRig` removed from one breaks
274
+ * none. §3.1's "the discriminator is a component's presence" then holds only
275
+ * for a character spawned whole, which is the one case a spec constructs and
276
+ * the least likely one in a scene where a model loads asynchronously.
277
+ *
278
+ * Both edges re-seed. The removing edge also gives the joints back first,
279
+ * because the instance that knows which they were is about to stop being a
280
+ * rig.
281
+ * @type {EntityObserver}
282
+ * @private
283
+ */
284
+ __rig_observer = new EntityObserver(
285
+ [Cloth, ClothRig],
286
+ (cloth, rig, entity) => this.reseed(entity),
287
+ (cloth, rig, entity) => this.#rig_removed(entity),
288
+ this
289
+ );
290
+
291
+ /**
292
+ * Resolve the systems a cloth world reads. Called from the owning system's
293
+ * `startup`.
294
+ *
295
+ * @param {EntityManager} entityManager
296
+ */
297
+ attach(entityManager) {
298
+ this.entityManager = entityManager;
299
+
300
+ this.attachments = entityManager.getSystem(TransformAttachmentSystem);
301
+
302
+ assert.notNull(
303
+ this.attachments,
304
+ 'ClothSystem needs a TransformAttachmentSystem: the subtree walk is its parent-to-children index'
305
+ );
306
+
307
+ // Optional by design. A scene with no ClothColliderSystem gets cloth
308
+ // that does not collide, rather than cloth that throws.
309
+ this.colliders = entityManager.getSystem(ClothColliderSystem) ?? null;
310
+
311
+ if (this.models === null) {
312
+ this.models = entityManager.getSystem(MeshSystem) ?? null;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * @param {Cloth} cloth
318
+ * @param {Transform64} transform
319
+ * @param {number} entity
320
+ * @returns {ClothInstance}
321
+ */
322
+ link(cloth, transform, entity) {
323
+ const instance = new ClothInstance();
324
+
325
+ instance.entity = entity;
326
+ instance.cloth = cloth;
327
+ instance.transform = transform;
328
+ instance.shared_state = this.shared_state;
329
+
330
+ this.instances.push(instance);
331
+ this.__by_entity.set(entity, instance);
332
+
333
+ return instance;
334
+ }
335
+
336
+ /**
337
+ * Watch this dataset for a {@link ClothRig} arriving at or leaving a live
338
+ * cloth. Called from the owning system's `handleDatasetAttached`; see
339
+ * {@link ClothWorld#__rig_observer} for why the system's own dependency
340
+ * tuple cannot answer this.
341
+ *
342
+ * @param {EntityComponentDataset} dataset
343
+ * @returns {void}
344
+ */
345
+ attach_dataset(dataset) {
346
+ dataset.addObserver(this.__rig_observer, true);
347
+ }
348
+
349
+ /**
350
+ * @param {EntityComponentDataset} dataset
351
+ * @returns {void}
352
+ */
353
+ detach_dataset(dataset) {
354
+ dataset.removeObserver(this.__rig_observer, false);
355
+ }
356
+
357
+ /**
358
+ * @param {number} entity
359
+ * @returns {ClothInstance|undefined} the instance that was removed
360
+ */
361
+ unlink(entity) {
362
+ const instance = this.__by_entity.get(entity);
363
+
364
+ if (instance === undefined) {
365
+ return undefined;
366
+ }
367
+
368
+ // Give back what the seeding took. Seeding is not a read: it revokes
369
+ // the GPU's authority over a rig's joints, and it leaves the entity
370
+ // path's authored local transforms nowhere but inside the cloth,
371
+ // because `cloth_write_back` overwrites them every step. A cloth
372
+ // removed without this leaves joints nothing is writing and a subtree
373
+ // frozen in the shape the solver stopped in.
374
+ this.#release(instance);
375
+
376
+ this.__by_entity.delete(entity);
377
+
378
+ const index = this.instances.indexOf(instance);
379
+
380
+ if (index >= 0) {
381
+ this.instances.splice(index, 1);
382
+ }
383
+
384
+ return instance;
385
+ }
386
+
387
+ /**
388
+ * Hand back whatever this instance's seeding took from the scene.
389
+ *
390
+ * @param {ClothInstance} instance
391
+ * @private
392
+ */
393
+ #release(instance) {
394
+ if (!instance.seeded) {
395
+ return;
396
+ }
397
+
398
+ const ecd = this.entityManager === null ? null : this.entityManager.dataset;
399
+
400
+ if (ecd === null || ecd === undefined) {
401
+ return;
402
+ }
403
+
404
+ if (instance.rig === null) {
405
+ cloth_release_subtree(instance, ecd);
406
+ } else {
407
+ cloth_release_rig(instance, ecd);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * A {@link ClothRig} has left a live cloth: give its joints back, then
413
+ * re-seed, which without a rig is the entity path.
414
+ *
415
+ * @param {number} entity
416
+ * @private
417
+ */
418
+ #rig_removed(entity) {
419
+ const instance = this.__by_entity.get(entity);
420
+
421
+ if (instance === undefined) {
422
+ return;
423
+ }
424
+
425
+ this.#release(instance);
426
+
427
+ instance.rig = null;
428
+
429
+ this.reseed(entity);
430
+ }
431
+
432
+ /**
433
+ * Throw away a cloth's binding so the next step walks its subtree again.
434
+ *
435
+ * The walk is done once, because a rope's links do not come and go. When
436
+ * they do — a chain gains a link, a rig is rebuilt — this is how you say so,
437
+ * and it is the same contract `TransformAttachment#parent` already carries:
438
+ * the structure is immutable while it is attached, and changing it is an
439
+ * explicit act rather than something the engine polls for.
440
+ *
441
+ * @param {number} entity
442
+ * @returns {ClothInstance|undefined} the instance that will re-seed
443
+ */
444
+ reseed(entity) {
445
+ const instance = this.__by_entity.get(entity);
446
+
447
+ if (instance === undefined) {
448
+ return undefined;
449
+ }
450
+
451
+ instance.seeded = false;
452
+ instance.anchor_valid = false;
453
+
454
+ return instance;
455
+ }
456
+
457
+ /**
458
+ * Wake a sleeping cloth. Anchor motion and a `ClothDynamics` change do this
459
+ * on their own; a caller that has changed the world in some way the cloth
460
+ * cannot see — M6's wind, for one — says so here.
461
+ *
462
+ * @param {number} entity
463
+ * @returns {void}
464
+ */
465
+ wake(entity) {
466
+ const instance = this.__by_entity.get(entity);
467
+
468
+ if (instance !== undefined) {
469
+ instance.asleep = false;
470
+ instance.still_steps = 0;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * @param {number} entity
476
+ * @returns {ClothInstance|undefined}
477
+ */
478
+ instanceOf(entity) {
479
+ return this.__by_entity.get(entity);
480
+ }
481
+
482
+ /**
483
+ * Everything that happens once per tick rather than once per cloth.
484
+ *
485
+ * @param {EntityComponentDataset} ecd
486
+ * @param {number} dt seconds
487
+ */
488
+ begin(ecd, dt) {
489
+ // Owed to the clock, and spent by the first `advance` — see {@link time}
490
+ // for why the two are not the same moment. Assigned rather than set, so a
491
+ // world whose source is taken away does not carry a debt.
492
+ this.__wind_pending = this.wind === null ? 0 : dt;
493
+
494
+ // The index is refreshed here rather than in its own system's fixed
495
+ // update, because the engine's ordering cannot put a reader after a
496
+ // writer of the same component and `ClothColliderSystem` reads strictly
497
+ // less than this system does. §4.1's conclusion, applied a second time:
498
+ // the answer is not a third mechanism, it is one call site.
499
+ if (this.colliders !== null) {
500
+ this.colliders.refresh();
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Make sure this cloth has a `ClothState` that matches its current
506
+ * `ClothDynamics`, seeding or re-seeding it if not.
507
+ *
508
+ * @param {ClothInstance} instance
509
+ * @param {EntityComponentDataset} ecd
510
+ * @param {number} dt seconds
511
+ * @returns {boolean} whether the cloth is ready to be stepped — `false` for
512
+ * one that has been refused, or a rig whose model has not loaded yet
513
+ */
514
+ ensure_seeded(instance, ecd, dt) {
515
+ if (instance.refused) {
516
+ return false;
517
+ }
518
+
519
+ if (!instance.seeded) {
520
+ this.#seed(instance, ecd, dt);
521
+
522
+ if (!instance.seeded) {
523
+ // A rig whose model has not finished loading. Nothing is
524
+ // wrong and nothing is retried-with-backoff: the next step
525
+ // asks again, and a model that never loads costs one map
526
+ // lookup a step.
527
+ return false;
528
+ }
529
+ } else if (!instance.cloth.dynamics.equals(instance.dynamics_seen)) {
530
+ if (cloth_dynamics_change_needs_reseed(instance.dynamics_seen, instance.cloth.dynamics)) {
531
+ this.#seed(instance, ecd, dt);
532
+ } else {
533
+ cloth_reparameterise(instance, dt);
534
+ }
535
+
536
+ instance.asleep = false;
537
+ instance.still_steps = 0;
538
+ }
539
+
540
+ return true;
541
+ }
542
+
543
+ /**
544
+ * Take what the step produced: the sleep test, and the pose onto whichever
545
+ * hierarchy this cloth was built on.
546
+ *
547
+ * @param {ClothInstance} instance
548
+ * @param {EntityComponentDataset} ecd
549
+ * @returns {void}
550
+ */
551
+ finish(instance, ecd) {
552
+ const result = instance.state.result;
553
+
554
+ instance.contact_count = result[ClothStepResult.CONTACT_COUNT];
555
+ instance.self_pairs = result[ClothStepResult.SELF_PAIRS];
556
+
557
+ if ((instance.cloth.flags & ClothFlags.NeverSleep) === 0) {
558
+ if (cloth_inside_sleep_window(instance)) {
559
+ instance.still_steps++;
560
+
561
+ if (instance.still_steps >= CLOTH_SLEEP_STEPS) {
562
+ instance.asleep = true;
563
+ }
564
+ } else {
565
+ instance.still_steps = 0;
566
+ }
567
+ }
568
+
569
+ instance.blend_seen = instance.cloth.blend;
570
+
571
+ if (instance.rig === null) {
572
+ cloth_write_back(instance, ecd);
573
+ } else {
574
+ cloth_write_back_rig(instance, ecd);
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Walk whichever hierarchy this cloth is built on.
580
+ *
581
+ * **The discriminator is a component's presence** (§3.1): `Cloth` alone
582
+ * simulates the entity's own transform subtree, `Cloth` + {@link ClothRig}
583
+ * simulates the joints of the skinned model the entity is showing. There is
584
+ * no mode field anywhere, and the solver does not know which ran.
585
+ *
586
+ * A rig can fail to seed for reasons that are not errors — the model has
587
+ * not loaded, or nothing in the manager hands out model instances — and one
588
+ * that does is left unseeded for the next step to try again. A rig that
589
+ * fails for a reason that *is* an error says so once and then stops, because
590
+ * a proxy that does not fit its skin will not start fitting it.
591
+ *
592
+ * @param {ClothInstance} instance
593
+ * @param {EntityComponentDataset} ecd
594
+ * @param {number} dt
595
+ * @private
596
+ */
597
+ #seed(instance, ecd, dt) {
598
+ const rig = ecd.getComponent(instance.entity, ClothRig);
599
+
600
+ if (rig === undefined || rig === null) {
601
+ instance.rig = null;
602
+
603
+ cloth_seed_subtree(instance, ecd, this.attachments, dt);
604
+
605
+ return;
606
+ }
607
+
608
+ if (rig.proxy === null) {
609
+ // Present and not ready. The same branch as a model that has not
610
+ // loaded, and for the same reason: a proxy filled in a few ticks
611
+ // after the component is a rig that is arriving, not a rig that is
612
+ // absent. Treating it as absent walks the character's own transform
613
+ // subtree — which under a spawned model is the whole skeleton — and
614
+ // seeds a skeleton as one rope, throwing in a dev build through
615
+ // `gpu_authority_assert_cpu` and simulating it in a shipped one.
616
+ return;
617
+ }
618
+
619
+ if (this.models === null) {
620
+ this.#refuse(instance, `entity ${instance.entity} has a ClothRig and nothing hands out model instances`);
621
+
622
+ return;
623
+ }
624
+
625
+ const model = this.models.instance_of(instance.entity);
626
+
627
+ if (model === null || model === undefined) {
628
+ // Still loading. Not an error, and not worth a message.
629
+ return;
630
+ }
631
+
632
+ const skin = model.skins[rig.skin];
633
+
634
+ const error = rig.proxy.checkAgainst(skin);
635
+
636
+ if (error !== "") {
637
+ this.#refuse(instance, `entity ${instance.entity}: ${error}`);
638
+
639
+ return;
640
+ }
641
+
642
+ instance.rig = rig;
643
+
644
+ cloth_seed_rig(instance, rig.proxy, skin.joints, ecd, dt);
645
+ }
646
+
647
+ /**
648
+ * Say once why a rig will not seed, and stop asking.
649
+ *
650
+ * @param {ClothInstance} instance
651
+ * @param {string} message
652
+ * @private
653
+ */
654
+ #refuse(instance, message) {
655
+ if (instance.refused) {
656
+ return;
657
+ }
658
+
659
+ instance.refused = true;
660
+
661
+ console.warn(`ClothSystem: ${message}`);
662
+ }
663
+
664
+ /**
665
+ * Carry one seeded cloth to the edge of its step and write the parameters
666
+ * the step will run on: whatever the anchor did to the local frame, where
667
+ * the leash rows measure from, and the colliders within reach.
668
+ *
669
+ * @param {ClothInstance} instance
670
+ * @param {number} dt seconds
671
+ * @param {Float64Array} params
672
+ * @param {number} offset first word of this cloth's block in `params`
673
+ * @returns {boolean} whether the cloth should be stepped — `false` for one
674
+ * that is asleep, and then nothing has been written to `params`
675
+ */
676
+ advance(instance, dt, params, offset) {
677
+ // This cloth's own clock, moved before anything reads it — including
678
+ // the wake tests below, which are asking about the step that would
679
+ // follow. See {@link ClothInstance#age}.
680
+ instance.age += dt;
681
+
682
+ if (this.__wind_pending !== 0) {
683
+ this.time += this.__wind_pending;
684
+
685
+ this.__wind_pending = 0;
686
+
687
+ // Once a tick rather than once a cloth: a source that reads the
688
+ // dataset — every live fluid field and its world box — walks it here
689
+ // and every cloth then reads the cache. `FluidSystem` makes the same
690
+ // trade for its own effector broadphase.
691
+ //
692
+ // Here rather than in `begin` because of the clock, and {@link time}
693
+ // is where that is argued. Above the sleep tests deliberately: a
694
+ // world whose every cloth is asleep still has weather, and §10's
695
+ // "sleep wakes on a wind change" is the whole reason it needs to.
696
+ if (this.wind !== null) {
697
+ this.wind.begin(
698
+ this.time,
699
+ this.entityManager === null ? null : this.entityManager.dataset
700
+ );
701
+ }
702
+ }
703
+
704
+ const transform = instance.rig === null
705
+ ? instance.transform
706
+ : this.#anchor_pose(instance);
707
+
708
+ const tx = transform[TRANSFORM64_TRANSLATION_OFFSET];
709
+ const ty = transform[TRANSFORM64_TRANSLATION_OFFSET + 1];
710
+ const tz = transform[TRANSFORM64_TRANSLATION_OFFSET + 2];
711
+
712
+ const qx = transform[TRANSFORM64_ROTATION_OFFSET];
713
+ const qy = transform[TRANSFORM64_ROTATION_OFFSET + 1];
714
+ const qz = transform[TRANSFORM64_ROTATION_OFFSET + 2];
715
+ const qw = transform[TRANSFORM64_ROTATION_OFFSET + 3];
716
+
717
+ const previous_translation = instance.anchor_translation;
718
+ const previous_rotation = instance.anchor_rotation;
719
+
720
+ if (instance.cloth.blend !== instance.blend_seen) {
721
+ instance.asleep = false;
722
+ instance.still_steps = 0;
723
+ }
724
+
725
+ const moved = instance.anchor_valid && (
726
+ previous_translation[0] !== tx
727
+ || previous_translation[1] !== ty
728
+ || previous_translation[2] !== tz
729
+ || previous_rotation[0] !== qx
730
+ || previous_rotation[1] !== qy
731
+ || previous_rotation[2] !== qz
732
+ || previous_rotation[3] !== qw
733
+ );
734
+
735
+ if (moved) {
736
+ instance.asleep = false;
737
+ instance.still_steps = 0;
738
+ }
739
+
740
+ if (instance.asleep && this.colliders !== null && instance.state !== null) {
741
+ // §7 P5's "wake on collider motion". A sleeping cloth is neither
742
+ // stepped nor written back, so something has to notice the world it
743
+ // was resting against moving out from under it, and a broadphase
744
+ // query plus a pose comparison is three orders of magnitude cheaper
745
+ // than the step it decides whether to skip.
746
+ if (cloth_colliders_moved(instance, this.colliders.index)) {
747
+ instance.asleep = false;
748
+ instance.still_steps = 0;
749
+ }
750
+ }
751
+
752
+ if (instance.asleep && this.wind !== null && instance.state !== null
753
+ && instance.cloth.dynamics.drag > 0) {
754
+ // §10's "sleep wakes on a wind change, so a settled banner does not
755
+ // sleep through a gust". One sample at the cloth's centre against
756
+ // the air the last applied step saw — the same shape as the collider
757
+ // test above, and the same argument: asking is orders of magnitude
758
+ // cheaper than the step it decides whether to skip.
759
+ if (cloth_air_changed(
760
+ instance.state, this.wind,
761
+ instance.anchor_translation, instance.anchor_rotation,
762
+ instance.air_seen, CLOTH_SLEEP_SPEED_RATIO * instance.reach,
763
+ instance.age
764
+ )) {
765
+ instance.asleep = false;
766
+ instance.still_steps = 0;
767
+ }
768
+ }
769
+
770
+ if (instance.asleep) {
771
+ // Nothing is stepped and nothing is written: the pose on the
772
+ // subtree is already the pose, and rewriting it is what would make
773
+ // §7 P5's "bit-identical across the following 300 steps" an
774
+ // aspiration rather than a fact.
775
+ return false;
776
+ }
777
+
778
+ if (instance.anchor_valid) {
779
+ const displacement = Math.hypot(tx - previous_translation[0], ty - previous_translation[1], tz - previous_translation[2]);
780
+
781
+ const teleport = displacement > Math.max(
782
+ CLOTH_TELEPORT_REACH * instance.reach,
783
+ CLOTH_TELEPORT_FLOOR
784
+ );
785
+
786
+ if (teleport) {
787
+ this.#teleport(instance);
788
+ } else {
789
+ move_local_frame(
790
+ instance,
791
+ tx, ty, tz, qx, qy, qz, qw,
792
+ previous_translation, previous_rotation
793
+ );
794
+ }
795
+ }
796
+
797
+ previous_translation[0] = tx;
798
+ previous_translation[1] = ty;
799
+ previous_translation[2] = tz;
800
+
801
+ previous_rotation[0] = qx;
802
+ previous_rotation[1] = qy;
803
+ previous_rotation[2] = qz;
804
+ previous_rotation[3] = qw;
805
+
806
+ instance.anchor_valid = true;
807
+
808
+ refresh_leash_targets(instance);
809
+
810
+ const dynamics = instance.cloth.dynamics;
811
+
812
+ // Gravity is a world vector and the solve is in the anchor's frame.
813
+ const gravity = this.gravity;
814
+
815
+ v3_quaternion_apply_inverse(LOCAL_GRAVITY, 0, gravity[0], gravity[1], gravity[2], qx, qy, qz, qw);
816
+
817
+ const state = instance.state;
818
+
819
+ const limit = cloth_velocity_limit(
820
+ instance.shortest_row, dynamics.substeps, dynamics.iterations, dt
821
+ );
822
+
823
+ state.contact_radius = dynamics.thickness;
824
+
825
+ if (this.colliders !== null) {
826
+ // The gather's margin is what the narrowphase will reach for and
827
+ // nothing beyond it — `cloth_gather_reach` carries the derivation,
828
+ // and why the velocity clamp that used to stand here was both far
829
+ // too wide and missing two terms.
830
+ const reach = cloth_gather_reach(state, this.colliders.index, dt);
831
+
832
+ cloth_gather_colliders(instance, this.colliders.index, reach);
833
+
834
+ // What this cloth is about to step against, for the next wake test
835
+ // to compare with. Here rather than inside the gather because the
836
+ // wake test gathers too, and what it asks about is the set the last
837
+ // *step* saw.
838
+ cloth_remember_colliders(instance, reach);
839
+ } else {
840
+ state.collider_count = 0;
841
+
842
+ instance.collider_key_count = 0;
843
+ }
844
+
845
+ // §10. The one piece of a cloth's step that has to read the world and
846
+ // cannot cross to a worker, so it runs here in both systems and what
847
+ // crosses is the answer — three floats a particle, written into a region
848
+ // of the cloth's own shared buffer. `cloth_sample_air.js` is the
849
+ // argument.
850
+ let drag_response = 0;
851
+
852
+ if (this.wind !== null && dynamics.drag > 0) {
853
+ drag_response = cloth_drag_response(dynamics.drag, dt);
854
+
855
+ instance.air_varies = cloth_sample_air(
856
+ state, this.wind,
857
+ instance.anchor_translation, instance.anchor_rotation,
858
+ instance.air_seen, instance.age
859
+ );
860
+ } else {
861
+ instance.air_varies = false;
862
+ }
863
+
864
+ params[offset + ClothStepParam.DRAG_RESPONSE] = drag_response;
865
+
866
+ const self_collision = (dynamics.flags & ClothDynamicsFlags.SelfCollision) !== 0;
867
+
868
+ params[offset + ClothStepParam.DT] = dt;
869
+ params[offset + ClothStepParam.GRAVITY_X] = LOCAL_GRAVITY[0];
870
+ params[offset + ClothStepParam.GRAVITY_Y] = LOCAL_GRAVITY[1];
871
+ params[offset + ClothStepParam.GRAVITY_Z] = LOCAL_GRAVITY[2];
872
+ params[offset + ClothStepParam.SUBSTEPS] = dynamics.substeps;
873
+ params[offset + ClothStepParam.ITERATIONS] = dynamics.iterations;
874
+ params[offset + ClothStepParam.RETENTION] = cloth_velocity_retention(dynamics.damping, dt);
875
+ params[offset + ClothStepParam.CONTACT_RADIUS] = dynamics.thickness;
876
+ params[offset + ClothStepParam.COLLIDER_COUNT] = state.collider_count;
877
+ params[offset + ClothStepParam.SELF_COLLISION] = self_collision ? 1 : 0;
878
+
879
+ // Half the shortest row, so the row's diameter is one particle
880
+ // spacing. `ClothState#self_radius` carries why the fabric's own
881
+ // thickness is the wrong length here and `MEASUREMENTS.md` §31 is the
882
+ // sweep that settled it. A fabric thicker than its proxy is coarse
883
+ // keeps its thickness — and is then clamped under the closest pair
884
+ // the proxy has with no row between them, because a diameter that
885
+ // reaches that pair puts a cloth which is not folded at all in
886
+ // permanent contact with itself (§36, §37).
887
+ params[offset + ClothStepParam.SELF_RADIUS] = self_collision
888
+ ? Math.min(
889
+ Math.max(dynamics.thickness, instance.shortest_row * CLOTH_SELF_RADIUS_MIN_RATIO),
890
+ instance.closest_free_pair * CLOTH_SELF_RADIUS_MAX_RATIO
891
+ )
892
+ : 0;
893
+
894
+ // Ramp back from nothing over the window. The first step after a
895
+ // reset is allowed no velocity at all, which is what stops a
896
+ // re-seeded cloth from snapping taut and whipping.
897
+ //
898
+ // After the gather, which wants the cloth's *unramped* reach: the
899
+ // broadphase margin is how far a particle could travel, and a cloth
900
+ // that is being held still for three steps is still a cloth whose
901
+ // colliders have to be found before it is let go.
902
+ let clamp_limit = limit;
903
+
904
+ if (instance.stabilise_remaining > 0) {
905
+ clamp_limit *= (CLOTH_STABILISE_STEPS - instance.stabilise_remaining) / CLOTH_STABILISE_STEPS;
906
+
907
+ instance.stabilise_remaining--;
908
+ }
909
+
910
+ params[offset + ClothStepParam.VELOCITY_LIMIT] = clamp_limit;
911
+
912
+ return true;
913
+ }
914
+
915
+ /**
916
+ * The anchor joint's world pose, computed on the CPU from the clips the
917
+ * entity is playing.
918
+ *
919
+ * **Computed, never read back**, for two reasons that are both the engine's
920
+ * own. A joint of an animated skeleton is the GPU's (`GPUStateAuthority`),
921
+ * so nothing maintains its CPU-side world and reading it gives its rest
922
+ * pose under whatever the CPU last composed. And GPU readback is a frame
923
+ * late and asynchronous, so anything depending on it would depend on frame
924
+ * pacing — which is why `query_entity_node_world_pose` exists and is
925
+ * documented as the only sanctioned way to ask this question. A cape and a
926
+ * scabbard on the same shoulder get their answer from the same evaluator,
927
+ * off the same clock, which is what keeps them on the same shoulder.
928
+ *
929
+ * @param {ClothInstance} instance
930
+ * @returns {Transform64}
931
+ * @private
932
+ */
933
+ #anchor_pose(instance) {
934
+ const playbacks = PLAYBACKS;
935
+
936
+ playbacks.length = 0;
937
+
938
+ collect_entity_playbacks(playbacks, this.entityManager, instance.entity);
939
+
940
+ t64_evaluate_world(instance.anchor_pose, this.entityManager.dataset, instance.node_joint[0], playbacks);
941
+
942
+ playbacks.length = 0;
943
+
944
+ return instance.anchor_pose;
945
+ }
946
+
947
+ /**
948
+ * @param {ClothInstance} instance
949
+ * @private
950
+ */
951
+ #teleport(instance) {
952
+ instance.teleport_count++;
953
+
954
+ if ((instance.cloth.flags & ClothFlags.TeleportKeep) !== 0) {
955
+ // `Keep` carries the cloth's shape across the jump. Particles are
956
+ // stored relative to the anchor, so that is exactly what happens if
957
+ // this function does nothing to them — the frame moved and they did
958
+ // not, which is MagicaCloth2's rigid delta expressed in the frame
959
+ // that makes it free.
960
+ return;
961
+ }
962
+
963
+ const state = instance.state;
964
+
965
+ const node_count = instance.node_count;
966
+ const animated = instance.animated_position;
967
+ const particle_of_node = instance.particle_of_node;
968
+
969
+ for (let node = 0; node < node_count; node++) {
970
+ const p = particle_of_node[node] * 3;
971
+ const n = node * 3;
972
+
973
+ state.position[p] = animated[n];
974
+ state.position[p + 1] = animated[n + 1];
975
+ state.position[p + 2] = animated[n + 2];
976
+ }
977
+
978
+ state.position_previous.set(state.position);
979
+ state.velocity.fill(0);
980
+ state.inertia_blend.fill(0);
981
+
982
+ // The multipliers describe forces in a configuration that no longer
983
+ // exists. Carrying them across would apply the old pose's tension to
984
+ // the new one on the first iteration, which is a whip with extra steps.
985
+ state.stretch_lambda.fill(0);
986
+ state.bend_lambda.fill(0);
987
+ state.leash_lambda.fill(0);
988
+
989
+ instance.stabilise_remaining = CLOTH_STABILISE_STEPS;
990
+ }
991
+ }
992
+
993
+ /**
994
+ * The clip set handed to the pose evaluator, rebuilt per call from the
995
+ * animation systems' own entries. Nothing in here outlives the call.
996
+ * @type {PosePlayback[]}
997
+ * @private
998
+ */
999
+ const PLAYBACKS = [];
1000
+
1001
+ /**
1002
+ * @type {Float64Array}
1003
+ * @private
1004
+ */
1005
+ const LOCAL_GRAVITY = new Float64Array(3);
1006
+
1007
+ /**
1008
+ * @type {Float64Array}
1009
+ * @private
1010
+ */
1011
+ const FRAME = new Float64Array(8);
1012
+
1013
+ /**
1014
+ * Carry the particles across a change of anchor pose, by however much of it the
1015
+ * fabric is willing to feel. §7 P7.
1016
+ *
1017
+ * A particle at local `p` was at world `t_prev + q_prev·p`. Holding that world
1018
+ * position fixed, its local coordinates become `D + Q·p` with
1019
+ * `D = q_now⁻¹·(t_prev − t_now)` and `Q = q_now⁻¹ ⊗ q_prev`. Feeling *all* of
1020
+ * the anchor's motion is taking that; feeling *none* is leaving `p` alone; and
1021
+ * `inertia` is the lerp between them. Velocity is carried the same way, minus
1022
+ * the translation, since a translation of the frame does not rotate a vector.
1023
+ *
1024
+ * `D` and `Q` are limited before the lerp. The limits are the cloth's own reach
1025
+ * per step and a fixed angle per step, which is what makes an anchor that
1026
+ * teleports a metre — or spins — something a cape *lags behind* rather than
1027
+ * something it is fired out of.
1028
+ *
1029
+ * @param {ClothInstance} instance
1030
+ * @param {number} tx
1031
+ * @param {number} ty
1032
+ * @param {number} tz
1033
+ * @param {number} qx
1034
+ * @param {number} qy
1035
+ * @param {number} qz
1036
+ * @param {number} qw
1037
+ * @param {Float64Array} previous_translation
1038
+ * @param {Float64Array} previous_rotation
1039
+ * @private
1040
+ */
1041
+ function move_local_frame(
1042
+ instance,
1043
+ tx, ty, tz, qx, qy, qz, qw,
1044
+ previous_translation, previous_rotation
1045
+ ) {
1046
+ let inertia = instance.cloth.dynamics.inertia;
1047
+
1048
+ if (inertia <= 0) {
1049
+ return;
1050
+ }
1051
+
1052
+ if (inertia > 1) {
1053
+ inertia = 1;
1054
+ }
1055
+
1056
+ v3_quaternion_apply_inverse(
1057
+ FRAME, 0,
1058
+ previous_translation[0] - tx,
1059
+ previous_translation[1] - ty,
1060
+ previous_translation[2] - tz,
1061
+ qx, qy, qz, qw
1062
+ );
1063
+
1064
+ const travel_limit = CLOTH_ANCHOR_TRAVEL_REACH * instance.reach;
1065
+
1066
+ const travel = Math.hypot(FRAME[0], FRAME[1], FRAME[2]);
1067
+
1068
+ if (travel > travel_limit && travel > 0) {
1069
+ const scale = travel_limit / travel;
1070
+
1071
+ FRAME[0] *= scale;
1072
+ FRAME[1] *= scale;
1073
+ FRAME[2] *= scale;
1074
+ }
1075
+
1076
+ quat3_multiply(
1077
+ FRAME, 4,
1078
+ -qx, -qy, -qz, qw,
1079
+ previous_rotation[0], previous_rotation[1], previous_rotation[2], previous_rotation[3]
1080
+ );
1081
+
1082
+ // `|w|` is `cos(θ/2)`, so this is the angle test without an `acos`.
1083
+ const half_angle_cos = FRAME[7] < 0 ? -FRAME[7] : FRAME[7];
1084
+
1085
+ if (half_angle_cos < Math.cos(CLOTH_ANCHOR_ROTATION_LIMIT * 0.5)) {
1086
+ const angle = 2 * Math.acos(half_angle_cos > 1 ? 1 : half_angle_cos);
1087
+
1088
+ quat3_nlerp(
1089
+ FRAME, 4,
1090
+ 0, 0, 0, 1,
1091
+ FRAME[4], FRAME[5], FRAME[6], FRAME[7],
1092
+ CLOTH_ANCHOR_ROTATION_LIMIT / angle
1093
+ );
1094
+ }
1095
+
1096
+ const state = instance.state;
1097
+
1098
+ const particle_count = state.particle_count;
1099
+
1100
+ const position = state.position;
1101
+ const velocity = state.velocity;
1102
+
1103
+ const dx = FRAME[0];
1104
+ const dy = FRAME[1];
1105
+ const dz = FRAME[2];
1106
+
1107
+ const rx = FRAME[4];
1108
+ const ry = FRAME[5];
1109
+ const rz = FRAME[6];
1110
+ const rw = FRAME[7];
1111
+
1112
+ for (let i = 0; i < particle_count; i++) {
1113
+ const p = i * 3;
1114
+
1115
+ v3_quaternion_apply(CARRIED, 0, position[p], position[p + 1], position[p + 2], rx, ry, rz, rw);
1116
+
1117
+ position[p] += inertia * (CARRIED[0] + dx - position[p]);
1118
+ position[p + 1] += inertia * (CARRIED[1] + dy - position[p + 1]);
1119
+ position[p + 2] += inertia * (CARRIED[2] + dz - position[p + 2]);
1120
+
1121
+ v3_quaternion_apply(CARRIED, 0, velocity[p], velocity[p + 1], velocity[p + 2], rx, ry, rz, rw);
1122
+
1123
+ velocity[p] += inertia * (CARRIED[0] - velocity[p]);
1124
+ velocity[p + 1] += inertia * (CARRIED[1] - velocity[p + 1]);
1125
+ velocity[p + 2] += inertia * (CARRIED[2] - velocity[p + 2]);
1126
+ }
1127
+
1128
+ // The anchor is pinned and is the origin of the frame it anchors, so it is
1129
+ // the one particle that must not be carried anywhere.
1130
+ const anchor = instance.particle_of_node[0] * 3;
1131
+
1132
+ position[anchor] = 0;
1133
+ position[anchor + 1] = 0;
1134
+ position[anchor + 2] = 0;
1135
+
1136
+ velocity[anchor] = 0;
1137
+ velocity[anchor + 1] = 0;
1138
+ velocity[anchor + 2] = 0;
1139
+ }
1140
+
1141
+ /**
1142
+ * @type {Float64Array}
1143
+ * @private
1144
+ */
1145
+ const CARRIED = new Float64Array(3);
1146
+
1147
+ /**
1148
+ * Whether every particle is still within the sleep window's own anchor — §7
1149
+ * P5's "maximum particle displacement over a window", and the reason the test
1150
+ * is not the instantaneous speed it used to be.
1151
+ *
1152
+ * A cloth **resting on a collider** does not stop: its contact rows carry a
1153
+ * penalty that `γ = 1` never decays, and what it settles into is a bounded
1154
+ * limit cycle. Measured on an 8-link rope on a floor, that is a tenth of a
1155
+ * millimetre of travel a step — nothing anyone can see — at a peak speed of
1156
+ * 1.5e-2 m/s, eighteen times the threshold a speed test allowed. So the speed
1157
+ * test held four of the seven library fabrics awake indefinitely on a floor
1158
+ * while the same fabrics slept in 283 steps hanging free. Distance covered
1159
+ * since the window opened tells those two apart; a speed sampled at one instant
1160
+ * cannot. `MEASUREMENTS.md` §53.
1161
+ *
1162
+ * The anchor is re-taken whenever a particle leaves the threshold, so the
1163
+ * window measures "how far has anything moved since the cloth last moved"
1164
+ * rather than chopping time into fixed epochs a slow drift could hide inside.
1165
+ *
1166
+ * @param {ClothInstance} instance
1167
+ * @returns {boolean}
1168
+ * @private
1169
+ */
1170
+ function cloth_inside_sleep_window(instance) {
1171
+ const position = instance.state.position;
1172
+
1173
+ let reference = instance.sleep_reference;
1174
+
1175
+ if (reference === null || reference.length !== position.length) {
1176
+ // First step, or a re-seed that changed the particle count. There is no
1177
+ // window yet, so this step opens one.
1178
+ instance.sleep_reference = Float32Array.from(position);
1179
+
1180
+ return false;
1181
+ }
1182
+
1183
+ const threshold = CLOTH_SLEEP_DISPLACEMENT_RATIO * instance.reach;
1184
+
1185
+ const threshold_squared = threshold * threshold;
1186
+
1187
+ const particle_count = instance.state.particle_count;
1188
+
1189
+ for (let i = 0; i < particle_count; i++) {
1190
+ const p = i * 3;
1191
+
1192
+ const dx = position[p] - reference[p];
1193
+ const dy = position[p + 1] - reference[p + 1];
1194
+ const dz = position[p + 2] - reference[p + 2];
1195
+
1196
+ if (dx * dx + dy * dy + dz * dz > threshold_squared) {
1197
+ reference.set(position);
1198
+
1199
+ return false;
1200
+ }
1201
+ }
1202
+
1203
+ return true;
1204
+ }
1205
+
1206
+ /**
1207
+ * Point every leash row at where it measures from: a slack row at the
1208
+ * particle's animated position, a tether at the anchor.
1209
+ *
1210
+ * @param {ClothInstance} instance
1211
+ * @private
1212
+ */
1213
+ function refresh_leash_targets(instance) {
1214
+ const state = instance.state;
1215
+
1216
+ const target = state.leash_target;
1217
+
1218
+ const animated = instance.animated_position;
1219
+
1220
+ const anchor = instance.particle_of_node[0] * 3;
1221
+
1222
+ const anchor_x = state.position[anchor];
1223
+ const anchor_y = state.position[anchor + 1];
1224
+ const anchor_z = state.position[anchor + 2];
1225
+
1226
+ const node_count = instance.node_count;
1227
+
1228
+ const slack_row = instance.slack_row;
1229
+ const tether_row = instance.tether_row;
1230
+
1231
+ for (let node = 1; node < node_count; node++) {
1232
+ const tether = tether_row[node];
1233
+
1234
+ if (tether >= 0) {
1235
+ target[tether * 3] = anchor_x;
1236
+ target[tether * 3 + 1] = anchor_y;
1237
+ target[tether * 3 + 2] = anchor_z;
1238
+ }
1239
+
1240
+ const slack = slack_row[node];
1241
+
1242
+ if (slack >= 0) {
1243
+ target[slack * 3] = animated[node * 3];
1244
+ target[slack * 3 + 1] = animated[node * 3 + 1];
1245
+ target[slack * 3 + 2] = animated[node * 3 + 2];
1246
+ }
1247
+ }
1248
+ }