@woosh/meep-engine 3.22.0 → 3.23.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 (267) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts +118 -0
  3. package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts.map +1 -0
  4. package/src/core/geom/3d/hash-grid/PointHashGrid3.js +283 -0
  5. package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
  6. package/src/core/geom/3d/shape/BoxShape3D.js +6 -17
  7. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
  8. package/src/core/geom/3d/shape/CapsuleShape3D.js +3 -14
  9. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  10. package/src/core/geom/3d/shape/CylinderShape3D.js +3 -21
  11. package/src/core/geom/3d/shape/PlaneShape3D.d.ts +105 -0
  12. package/src/core/geom/3d/shape/PlaneShape3D.d.ts.map +1 -0
  13. package/src/core/geom/3d/shape/PlaneShape3D.js +251 -0
  14. package/src/core/geom/3d/shape/SphereShape3D.js +3 -3
  15. package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
  16. package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
  17. package/src/core/geom/3d/shape/json/type_adapters.d.ts +14 -0
  18. package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
  19. package/src/core/geom/3d/shape/json/type_adapters.js +15 -0
  20. package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts +51 -0
  21. package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts.map +1 -0
  22. package/src/core/geom/3d/shape/sdf/sdf3_box.js +113 -0
  23. package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts +46 -0
  24. package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts.map +1 -0
  25. package/src/core/geom/3d/shape/sdf/sdf3_capsule.js +82 -0
  26. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts +60 -0
  27. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.js +126 -0
  29. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts +50 -0
  30. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts.map +1 -0
  31. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.js +53 -0
  32. package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts +52 -0
  33. package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts.map +1 -0
  34. package/src/core/geom/3d/shape/sdf/sdf3_plane.js +61 -0
  35. package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts +41 -0
  36. package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts.map +1 -0
  37. package/src/core/geom/3d/shape/sdf/sdf3_sphere.js +71 -0
  38. package/src/core/process/ConcurrencyGate.d.ts +103 -0
  39. package/src/core/process/ConcurrencyGate.d.ts.map +1 -0
  40. package/src/core/process/ConcurrencyGate.js +207 -0
  41. package/src/engine/asset/AssetManager.d.ts +10 -0
  42. package/src/engine/asset/AssetManager.d.ts.map +1 -1
  43. package/src/engine/asset/AssetManager.js +10 -0
  44. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +30 -13
  45. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +26 -0
  46. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -0
  47. package/src/engine/graphics3/pose/collect_entity_playbacks.js +40 -0
  48. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
  49. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +74 -101
  50. package/src/engine/physics/cloth/MEASUREMENTS.md +1287 -4
  51. package/src/engine/physics/cloth/PLAN.md +1967 -1581
  52. package/src/engine/physics/cloth/build/ClothProxy.d.ts +147 -0
  53. package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -0
  54. package/src/engine/physics/cloth/build/ClothProxy.js +207 -0
  55. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +70 -0
  56. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -0
  57. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +256 -0
  58. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +176 -0
  59. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -0
  60. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +435 -0
  61. package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts +12 -0
  62. package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts.map +1 -0
  63. package/src/engine/physics/cloth/collider/ClothColliderKind.js +38 -0
  64. package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts +120 -0
  65. package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts.map +1 -0
  66. package/src/engine/physics/cloth/collider/ClothColliderRecord.js +137 -0
  67. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +25 -0
  68. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -0
  69. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +190 -0
  70. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +44 -0
  71. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -0
  72. package/src/engine/physics/cloth/collider/cloth_collider_pack.js +173 -0
  73. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +94 -0
  74. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -0
  75. package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +275 -0
  76. package/src/engine/physics/cloth/ecs/ClothCollider.d.ts +126 -0
  77. package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -0
  78. package/src/engine/physics/cloth/ecs/ClothCollider.js +199 -0
  79. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +9 -0
  80. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts.map +1 -0
  81. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +32 -0
  82. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +114 -0
  83. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -0
  84. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +243 -0
  85. package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +66 -57
  86. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +109 -1
  87. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  88. package/src/engine/physics/cloth/ecs/ClothInstance.js +479 -363
  89. package/src/engine/physics/cloth/ecs/ClothRig.d.ts +95 -0
  90. package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -0
  91. package/src/engine/physics/cloth/ecs/ClothRig.js +140 -0
  92. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +30 -1
  93. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  94. package/src/engine/physics/cloth/ecs/ClothSystem.js +1021 -773
  95. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +90 -0
  96. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -0
  97. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +374 -0
  98. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts +32 -3
  99. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts.map +1 -1
  100. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.js +283 -254
  101. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +59 -0
  102. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -0
  103. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +328 -0
  104. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +13 -0
  105. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -0
  106. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +288 -0
  107. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +58 -0
  108. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -0
  109. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +199 -0
  110. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +8 -63
  111. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  112. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +317 -645
  113. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +61 -0
  114. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  115. package/src/engine/physics/cloth/ecs/cloth_write_back.js +212 -449
  116. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +49 -0
  117. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -0
  118. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +115 -0
  119. package/src/engine/physics/cloth/playground/README.md +155 -1
  120. package/src/engine/physics/cloth/playground/collide.html +178 -0
  121. package/src/engine/physics/cloth/playground/collide_build.d.ts +95 -0
  122. package/src/engine/physics/cloth/playground/collide_build.d.ts.map +1 -0
  123. package/src/engine/physics/cloth/playground/collide_build.js +315 -0
  124. package/src/engine/physics/cloth/playground/collide_main.d.ts +2 -0
  125. package/src/engine/physics/cloth/playground/collide_main.d.ts.map +1 -0
  126. package/src/engine/physics/cloth/playground/collide_main.js +720 -0
  127. package/src/engine/physics/cloth/playground/garment.html +182 -0
  128. package/src/engine/physics/cloth/playground/garment_build.d.ts +78 -0
  129. package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -0
  130. package/src/engine/physics/cloth/playground/garment_build.js +359 -0
  131. package/src/engine/physics/cloth/playground/garment_main.d.ts +2 -0
  132. package/src/engine/physics/cloth/playground/garment_main.d.ts.map +1 -0
  133. package/src/engine/physics/cloth/playground/garment_main.js +633 -0
  134. package/src/engine/physics/cloth/solver/ClothState.d.ts +208 -5
  135. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  136. package/src/engine/physics/cloth/solver/ClothState.js +302 -8
  137. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +87 -0
  138. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -0
  139. package/src/engine/physics/cloth/solver/cloth_contact_find.js +343 -0
  140. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +112 -0
  141. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -0
  142. package/src/engine/physics/cloth/solver/cloth_self_find.js +448 -0
  143. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts +4 -1
  144. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
  145. package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +16 -1
  146. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +112 -5
  147. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  148. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +117 -6
  149. package/src/engine/physics/cloth/solver/cloth_step.d.ts +40 -9
  150. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  151. package/src/engine/physics/cloth/solver/cloth_step.js +122 -10
  152. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +95 -0
  153. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -0
  154. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +285 -0
  155. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +129 -0
  156. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -0
  157. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +501 -0
  158. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +75 -0
  159. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -0
  160. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +202 -0
  161. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  162. package/src/shade/playground/vgeo_viewer/README.md +18 -0
  163. package/src/shade/playground/vgeo_viewer/main.js +87 -50
  164. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  165. package/src/shade/renderer/buffer/table/GPUDatabase.js +191 -62
  166. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +94 -2
  167. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  168. package/src/shade/renderer/buffer/table/GPUTypedTable.js +414 -2
  169. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts +14 -0
  170. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map +1 -0
  171. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js +13 -0
  172. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts +17 -0
  173. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts.map +1 -0
  174. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js +116 -0
  175. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +9 -4
  176. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +843 -734
  178. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts +12 -0
  179. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.js +13 -0
  181. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +17 -0
  182. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  183. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +185 -30
  184. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +17 -0
  185. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +141 -24
  186. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +204 -64
  187. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +110 -10
  188. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +19 -0
  189. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +19 -6
  190. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
  191. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +26 -7
  192. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +173 -14
  193. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  194. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +458 -51
  195. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +49 -13
  196. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
  197. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +52 -14
  198. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts +20 -0
  199. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts.map +1 -0
  200. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.js +19 -0
  201. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts +27 -0
  202. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts.map +1 -0
  203. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js +48 -0
  204. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts +29 -0
  205. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts.map +1 -0
  206. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js +60 -0
  207. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +8 -1
  208. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
  209. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +32 -2
  210. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +258 -0
  211. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +1 -0
  212. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +723 -0
  213. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +49 -0
  214. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +1 -0
  215. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +50 -0
  216. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  217. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  218. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts +3 -0
  219. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts.map +1 -1
  220. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +121 -21
  221. package/src/shade/renderer/texture/virtual/README.md +6 -4
  222. package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
  223. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +7 -0
  224. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.d.ts +3 -3
  225. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +3 -3
  226. package/src/engine/ecs/system/SystemExecutionPhase.d.ts +0 -9
  227. package/src/engine/ecs/system/SystemExecutionPhase.d.ts.map +0 -1
  228. package/src/engine/graphics/texture/virtual/NOTES.md +0 -128
  229. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts +0 -6
  230. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts.map +0 -1
  231. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.js +0 -5
  232. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +0 -49
  233. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +0 -1
  234. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +0 -264
  235. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts +0 -44
  236. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts.map +0 -1
  237. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.js +0 -228
  238. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts +0 -3
  239. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts.map +0 -1
  240. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.js +0 -73
  241. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts +0 -14
  242. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts.map +0 -1
  243. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.js +0 -85
  244. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts +0 -12
  245. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts.map +0 -1
  246. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.js +0 -63
  247. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts +0 -12
  248. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts.map +0 -1
  249. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.js +0 -243
  250. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts +0 -30
  251. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts.map +0 -1
  252. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.js +0 -39
  253. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts +0 -9
  254. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts.map +0 -1
  255. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.js +0 -24
  256. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts +0 -9
  257. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts.map +0 -1
  258. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +0 -26
  259. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts +0 -11
  260. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts.map +0 -1
  261. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.js +0 -12
  262. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts +0 -7
  263. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts.map +0 -1
  264. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.js +0 -16
  265. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts +0 -7
  266. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts.map +0 -1
  267. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.js +0 -35
@@ -0,0 +1,720 @@
1
+ import {
2
+ CCR_FRICTION,
3
+ CCR_INFLATION,
4
+ CCR_INTERP_QW,
5
+ CCR_INTERP_QX,
6
+ CCR_INTERP_QY,
7
+ CCR_INTERP_QZ,
8
+ CCR_INTERP_TX,
9
+ CCR_INTERP_TY,
10
+ CCR_INTERP_TZ,
11
+ CCR_STRIDE
12
+ } from "../collider/ClothColliderRecord.js";
13
+ import { ClothColliderKind } from "../collider/ClothColliderKind.js";
14
+ import {
15
+ cloth_collider_pose_at,
16
+ cloth_collider_signed_distance
17
+ } from "../collider/cloth_collider_sdf.js";
18
+ import { cloth_contact_find } from "../solver/cloth_contact_find.js";
19
+ import { CLOTH_SOLVER_TUNING } from "../solver/cloth_solver_constants.js";
20
+ import { cloth_step } from "../solver/cloth_step.js";
21
+ import { CLOTH_CONTACT_SLOTS } from "../solver/constraint/cloth_contact_row.js";
22
+ import { find_non_finite_cloth_state } from "../solver/find_non_finite_cloth_state.js";
23
+ import { build_cloth_playground_scene } from "./cloth_playground_build.js";
24
+ import { COLLIDER_SCENES, unsweep } from "./collide_build.js";
25
+ import { draw_cloth, PlaygroundCamera, project } from "./cloth_playground_draw.js";
26
+
27
+ /**
28
+ * # Cloth collision playground — M2
29
+ *
30
+ * `npm run dev --workspace @woosh/meep-engine`, then
31
+ * <http://localhost:5173/src/engine/physics/cloth/playground/collide.html>.
32
+ *
33
+ * The solver page (`index.html`) is `cloth_step` with a canvas attached; this
34
+ * is the same thing with a **packed collider table** in the argument list. No
35
+ * `EntityManager`, no `ClothColliderIndex`, no `ClothColliderSystem` — the
36
+ * scenes below hand the solver exactly what `cloth_gather_colliders` would, in
37
+ * the cloth's own frame, which is the layer M2's findings live in.
38
+ *
39
+ * Two of those findings are things to *watch* rather than read:
40
+ *
41
+ * - **the sweep**, which is not really about tunnelling. Turn it off on the
42
+ * swept-capsule scene and the capsule stops being *noticed*: the discovery
43
+ * pass evaluates at the end-of-step pose, where a fast collider has already
44
+ * gone by, so no row is ever created. The contact readout goes to zero while
45
+ * the capsule visibly passes through the sheet.
46
+ * - **the penalty floor**, which decides how hard a new contact hits. Drag
47
+ * `k_min ÷ k_max` to 1 on the ground scene and watch the cape slap; drag it
48
+ * back to 1/32 and it lands.
49
+ *
50
+ * @author Alex Goldring
51
+ * @copyright Company Named Limited (c) 2026
52
+ */
53
+
54
+ /** Display rate the fixed step is driven at. */
55
+ const FIXED_STEP = 1 / 60;
56
+
57
+ /** §17's target, for the budget bar. */
58
+ const BUDGET_MS = 2;
59
+
60
+ /** How many recent step timings the readout keeps. */
61
+ const TIMING_WINDOW = 120;
62
+
63
+ const element = (id) => document.getElementById(id);
64
+
65
+ const canvas = element('view');
66
+ const context = canvas.getContext('2d');
67
+
68
+ const camera = new PlaygroundCamera();
69
+
70
+ camera.target[1] = -0.35;
71
+ camera.distance = 1.5;
72
+
73
+ /**
74
+ * Everything the page can change. `scene`, `size` and `spacing` rebuild; the
75
+ * rest are read afresh every step.
76
+ */
77
+ const control = {
78
+ scene: 'sphere',
79
+ size: 22,
80
+ spacing: 0.03,
81
+ thickness: 0.006,
82
+ friction: 0.4,
83
+ inflation: 0,
84
+ substeps: 2,
85
+ iterations: 4,
86
+ k_floor: 1 / 32,
87
+ beta: CLOTH_SOLVER_TUNING.beta,
88
+ swept: true,
89
+ animate: true,
90
+ running: true
91
+ };
92
+
93
+ /** @type {{state: ClothState, triangle: number[]}} */
94
+ let cloth = null;
95
+
96
+ /** @type {import("./collide_build.js").ColliderScene} */
97
+ let colliders = null;
98
+
99
+ let time = 0;
100
+
101
+ let worst_penetration = 0;
102
+ let contact_rows = 0;
103
+
104
+ const timings = [];
105
+
106
+ const GRADIENT = new Float64Array(3);
107
+
108
+ // ------------------------------------------------------------------ build
109
+
110
+ function rebuild() {
111
+ cloth = build_cloth_playground_scene({
112
+ shape: 'sheet',
113
+ width: control.size,
114
+ height: control.size,
115
+ spacing: control.spacing,
116
+ mass: 0.4,
117
+ stretch_stiffness: Infinity,
118
+ bend_modulus: 1e-4,
119
+ pinned: 'top',
120
+ flat: false
121
+ });
122
+
123
+ cloth.state.contact_radius = control.thickness;
124
+
125
+ colliders = COLLIDER_SCENES[control.scene].build();
126
+
127
+ element('scene_blurb').textContent = COLLIDER_SCENES[control.scene].blurb;
128
+
129
+ time = 0;
130
+ worst_penetration = 0;
131
+ contact_rows = 0;
132
+
133
+ timings.length = 0;
134
+ }
135
+
136
+ // ------------------------------------------------------------------- step
137
+
138
+ function step_once() {
139
+ const state = cloth.state;
140
+
141
+ state.contact_radius = control.thickness;
142
+
143
+ for (let record = 0; record < colliders.count; record++) {
144
+ const r = record * CCR_STRIDE;
145
+
146
+ colliders.table[r + CCR_FRICTION] = control.friction;
147
+ colliders.table[r + CCR_INFLATION] = control.inflation;
148
+ }
149
+
150
+ if (control.animate && colliders.animate !== undefined) {
151
+ time += FIXED_STEP;
152
+
153
+ for (let record = 0; record < colliders.count; record++) {
154
+ colliders.animate(record, time);
155
+ }
156
+ }
157
+
158
+ if (!control.swept) {
159
+ // The previous pose becomes the current one, so there is nothing to
160
+ // interpolate and no travel to widen the discovery margin with.
161
+ for (let record = 0; record < colliders.count; record++) {
162
+ unsweep(colliders, record);
163
+ }
164
+ }
165
+
166
+ const tuning = {
167
+ alpha: CLOTH_SOLVER_TUNING.alpha,
168
+ beta: control.beta,
169
+ gamma: CLOTH_SOLVER_TUNING.gamma,
170
+ k_min_ratio: CLOTH_SOLVER_TUNING.k_max_ratio,
171
+ k_max_ratio: CLOTH_SOLVER_TUNING.k_max_ratio
172
+ };
173
+
174
+ const started = performance.now();
175
+
176
+ contact_rows = cloth_contact_find(state, colliders.table, colliders.keys, colliders.count);
177
+
178
+ cloth_step(
179
+ state, FIXED_STEP, 0, -9.81, 0,
180
+ control.substeps, control.iterations,
181
+ colliders.table, colliders.count,
182
+ { ...tuning, k_min_ratio: tuning.k_max_ratio * control.k_floor }
183
+ );
184
+
185
+ const elapsed = performance.now() - started;
186
+
187
+ timings.push(elapsed);
188
+
189
+ if (timings.length > TIMING_WINDOW) {
190
+ timings.shift();
191
+ }
192
+
193
+ const depth = penetration();
194
+
195
+ if (depth > worst_penetration) {
196
+ worst_penetration = depth;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @returns {number} how far inside any collider any particle is, right now
202
+ */
203
+ function penetration() {
204
+ const state = cloth.state;
205
+
206
+ cloth_collider_pose_at(colliders.table, colliders.count, 1);
207
+
208
+ let worst = 0;
209
+
210
+ for (let i = 0; i < state.particle_count; i++) {
211
+ const p = i * 3;
212
+
213
+ for (let record = 0; record < colliders.count; record++) {
214
+ const distance = cloth_collider_signed_distance(
215
+ GRADIENT, 0, colliders.table, record,
216
+ state.position[p], state.position[p + 1], state.position[p + 2]
217
+ );
218
+
219
+ const offset = state.contact_radius + colliders.table[record * CCR_STRIDE + CCR_INFLATION];
220
+
221
+ if (offset - distance > worst) {
222
+ worst = offset - distance;
223
+ }
224
+ }
225
+ }
226
+
227
+ return worst;
228
+ }
229
+
230
+ /**
231
+ * @returns {number} kinetic energy, J
232
+ */
233
+ function kinetic() {
234
+ const state = cloth.state;
235
+
236
+ let total = 0;
237
+
238
+ for (let i = 0; i < state.particle_count; i++) {
239
+ const w = state.mass_inverse[i];
240
+
241
+ if (w === 0) {
242
+ continue;
243
+ }
244
+
245
+ const p = i * 3;
246
+
247
+ total += 0.5 * (
248
+ state.velocity[p] * state.velocity[p] +
249
+ state.velocity[p + 1] * state.velocity[p + 1] +
250
+ state.velocity[p + 2] * state.velocity[p + 2]
251
+ ) / w;
252
+ }
253
+
254
+ return total;
255
+ }
256
+
257
+ // ------------------------------------------------------------------- draw
258
+
259
+ /**
260
+ * Colliders as wireframes over the cloth. Each is drawn from its own
261
+ * *interpolated* pose, so the swept toggle is visible rather than merely
262
+ * described: with the sweep on, the outline is where the substeps saw it.
263
+ *
264
+ * There is **no depth test**: the outline is painted over the cloth whether it
265
+ * is in front of it or behind. That is a property of the drawing rather than of
266
+ * the simulation, and it is worth knowing before reading a screenshot — a
267
+ * sphere behind a draped cape looks like a sphere poking through one. The
268
+ * readout's *resting penetration* is the number that says which it is.
269
+ */
270
+ function draw_colliders() {
271
+ const width = canvas.width;
272
+ const height = canvas.height;
273
+
274
+ context.save();
275
+ context.strokeStyle = '#e5b54a';
276
+ context.globalAlpha = 0.4;
277
+ context.setLineDash([5, 4]);
278
+ context.lineWidth = Math.max(1, width / 900);
279
+
280
+ const projected = new Float64Array(2);
281
+
282
+ for (let record = 0; record < colliders.count; record++) {
283
+ const r = record * CCR_STRIDE;
284
+
285
+ const kind = colliders.table[r + CCR_KIND_SLOT];
286
+
287
+ const outline = outline_of(record);
288
+
289
+ for (const loop of outline) {
290
+ context.beginPath();
291
+
292
+ for (let i = 0; i < loop.length; i += 3) {
293
+ to_world(WORLD, r, loop[i], loop[i + 1], loop[i + 2]);
294
+
295
+ project(projected, camera, WORLD[0], WORLD[1], WORLD[2], width, height);
296
+
297
+ if (i === 0) {
298
+ context.moveTo(projected[0], projected[1]);
299
+ } else {
300
+ context.lineTo(projected[0], projected[1]);
301
+ }
302
+ }
303
+
304
+ if (kind !== ClothColliderKind.Plane) {
305
+ context.closePath();
306
+ }
307
+
308
+ context.stroke();
309
+ }
310
+ }
311
+
312
+ context.restore();
313
+ }
314
+
315
+ /** The kind slot, named here so the drawing does not import the whole record. */
316
+ const CCR_KIND_SLOT = 0;
317
+
318
+ const WORLD = new Float64Array(3);
319
+
320
+ /**
321
+ * @param {Float64Array} out
322
+ * @param {number} r record slot offset
323
+ * @param {number} x local
324
+ * @param {number} y local
325
+ * @param {number} z local
326
+ */
327
+ function to_world(out, r, x, y, z) {
328
+ const qx = colliders.table[r + CCR_INTERP_QX];
329
+ const qy = colliders.table[r + CCR_INTERP_QY];
330
+ const qz = colliders.table[r + CCR_INTERP_QZ];
331
+ const qw = colliders.table[r + CCR_INTERP_QW];
332
+
333
+ const tx = 2 * (qy * z - qz * y);
334
+ const ty = 2 * (qz * x - qx * z);
335
+ const tz = 2 * (qx * y - qy * x);
336
+
337
+ out[0] = x + qw * tx + (qy * tz - qz * ty) + colliders.table[r + CCR_INTERP_TX];
338
+ out[1] = y + qw * ty + (qz * tx - qx * tz) + colliders.table[r + CCR_INTERP_TY];
339
+ out[2] = z + qw * tz + (qx * ty - qy * tx) + colliders.table[r + CCR_INTERP_TZ];
340
+ }
341
+
342
+ /**
343
+ * Line loops in the collider's own local frame, one flat `[x, y, z, ...]` array
344
+ * each. Drawn rather than triangulated because the point is to see the cloth
345
+ * through the collider.
346
+ *
347
+ * @param {number} record
348
+ * @returns {number[][]}
349
+ */
350
+ function outline_of(record) {
351
+ const shape = colliders.shape[record];
352
+
353
+ const table = colliders.table;
354
+ const r = record * CCR_STRIDE;
355
+
356
+ const kind = table[r + CCR_KIND_SLOT];
357
+
358
+ if (kind === ClothColliderKind.Sphere) {
359
+ return [ring(shape.radius, 0, 1), ring(shape.radius, 0, 2), ring(shape.radius, 1, 2)];
360
+ }
361
+
362
+ if (kind === ClothColliderKind.Capsule || kind === ClothColliderKind.Cylinder) {
363
+ const half = shape.height * 0.5;
364
+ const radius = shape.radius;
365
+
366
+ const loops = [
367
+ ring(radius, 0, 2, half),
368
+ ring(radius, 0, 2, -half)
369
+ ];
370
+
371
+ // Two side lines, plus the caps for a capsule.
372
+ loops.push([radius, half, 0, radius, -half, 0]);
373
+ loops.push([-radius, half, 0, -radius, -half, 0]);
374
+
375
+ if (kind === ClothColliderKind.Capsule) {
376
+ loops.push(arc(radius, half, 0, Math.PI));
377
+ loops.push(arc(radius, -half, Math.PI, Math.PI * 2));
378
+ }
379
+
380
+ return loops;
381
+ }
382
+
383
+ if (kind === ClothColliderKind.Box) {
384
+ const h = shape.half_extents;
385
+
386
+ return [
387
+ [-h.x, -h.y, -h.z, h.x, -h.y, -h.z, h.x, h.y, -h.z, -h.x, h.y, -h.z],
388
+ [-h.x, -h.y, h.z, h.x, -h.y, h.z, h.x, h.y, h.z, -h.x, h.y, h.z],
389
+ [-h.x, -h.y, -h.z, -h.x, -h.y, h.z],
390
+ [h.x, -h.y, -h.z, h.x, -h.y, h.z],
391
+ [h.x, h.y, -h.z, h.x, h.y, h.z],
392
+ [-h.x, h.y, -h.z, -h.x, h.y, h.z]
393
+ ];
394
+ }
395
+
396
+ // A plane: a grid on its own surface, which is all of it anyone can see.
397
+ const loops = [];
398
+
399
+ const extent = 1.2;
400
+
401
+ for (let i = -4; i <= 4; i++) {
402
+ const t = i * extent / 4;
403
+
404
+ loops.push([t, 0, -extent, t, 0, extent]);
405
+ loops.push([-extent, 0, t, extent, 0, t]);
406
+ }
407
+
408
+ return loops;
409
+ }
410
+
411
+ /**
412
+ * @param {number} radius
413
+ * @param {number} axis_a 0, 1 or 2
414
+ * @param {number} axis_b
415
+ * @param {number} [offset] along the remaining axis
416
+ * @returns {number[]}
417
+ */
418
+ function ring(radius, axis_a, axis_b, offset = 0) {
419
+ const out = [];
420
+
421
+ const other = 3 - axis_a - axis_b;
422
+
423
+ for (let i = 0; i < 24; i++) {
424
+ const angle = i / 24 * Math.PI * 2;
425
+
426
+ const point = [0, 0, 0];
427
+
428
+ point[axis_a] = Math.cos(angle) * radius;
429
+ point[axis_b] = Math.sin(angle) * radius;
430
+ point[other] = offset;
431
+
432
+ out.push(point[0], point[1], point[2]);
433
+ }
434
+
435
+ return out;
436
+ }
437
+
438
+ /**
439
+ * A cap arc in the XY plane, offset along Y.
440
+ * @param {number} radius
441
+ * @param {number} y
442
+ * @param {number} from radians
443
+ * @param {number} to
444
+ * @returns {number[]}
445
+ */
446
+ function arc(radius, y, from, to) {
447
+ const out = [];
448
+
449
+ for (let i = 0; i <= 12; i++) {
450
+ const angle = from + (to - from) * i / 12;
451
+
452
+ out.push(Math.cos(angle) * radius, y + Math.sin(angle) * radius, 0);
453
+ }
454
+
455
+ return out;
456
+ }
457
+
458
+ // ---------------------------------------------------------------- readout
459
+
460
+ function refresh_readout() {
461
+ const best = timings.length === 0 ? 0 : Math.min(...timings);
462
+
463
+ element('r_step').textContent = `${best.toFixed(3)} ms`;
464
+ element('r_contacts').textContent = String(contact_rows);
465
+ element('r_pen').textContent = `${(worst_penetration * 1000).toFixed(2)} mm`;
466
+ element('r_rest').textContent = `${(penetration() * 1000).toFixed(2)} mm`;
467
+ element('r_energy').textContent = kinetic().toExponential(2);
468
+
469
+ const offender = find_non_finite_cloth_state(cloth.state);
470
+
471
+ const finite = element('r_finite');
472
+
473
+ finite.textContent = offender === "" ? 'ok' : offender;
474
+ finite.className = offender === "" ? 'good' : 'bad';
475
+
476
+ if (offender !== "") {
477
+ control.running = false;
478
+ element('pause').textContent = 'resume';
479
+ }
480
+
481
+ const budget = element('budget');
482
+
483
+ budget.classList.toggle('over', best > BUDGET_MS);
484
+ budget.firstElementChild.style.width = `${Math.min(100, best / BUDGET_MS * 100)}%`;
485
+ }
486
+
487
+ // ------------------------------------------------------------------ frame
488
+
489
+ function frame() {
490
+ if (control.running) {
491
+ step_once();
492
+ }
493
+
494
+ draw_cloth(context, camera, cloth.state, cloth.triangle, {
495
+ view: 'shaded',
496
+ colour_of_particle: null,
497
+ grabbed: -1
498
+ });
499
+
500
+ draw_colliders();
501
+
502
+ refresh_readout();
503
+
504
+ requestAnimationFrame(frame);
505
+ }
506
+
507
+ // --------------------------------------------------------------- controls
508
+
509
+ const scene_select = element('scene');
510
+
511
+ for (const [name, entry] of Object.entries(COLLIDER_SCENES)) {
512
+ const option = document.createElement('option');
513
+
514
+ option.value = name;
515
+ option.textContent = entry.label;
516
+
517
+ scene_select.append(option);
518
+ }
519
+
520
+ scene_select.value = control.scene;
521
+
522
+ scene_select.addEventListener('change', () => {
523
+ control.scene = scene_select.value;
524
+
525
+ rebuild();
526
+ });
527
+
528
+ /**
529
+ * @param {string} id
530
+ * @param {string} key
531
+ * @param {function(number): string} format
532
+ * @param {boolean} rebuilds
533
+ */
534
+ function slider(id, key, format, rebuilds = false) {
535
+ const input = element(id);
536
+ const output = element(`${id}_out`);
537
+
538
+ input.value = String(control[key]);
539
+ output.textContent = format(control[key]);
540
+
541
+ input.addEventListener('input', () => {
542
+ control[key] = Number(input.value);
543
+
544
+ output.textContent = format(control[key]);
545
+
546
+ if (rebuilds) {
547
+ rebuild();
548
+ }
549
+ });
550
+
551
+ return input;
552
+ }
553
+
554
+ slider('size', 'size', (v) => `${v}²`, true);
555
+ slider('spacing', 'spacing', (v) => `${(v * 100).toFixed(1)} cm`, true);
556
+ slider('thickness', 'thickness', (v) => `${(v * 1000).toFixed(0)} mm`);
557
+ slider('friction', 'friction', (v) => v.toFixed(2));
558
+ slider('inflation', 'inflation', (v) => `${(v * 1000).toFixed(0)} mm`);
559
+ slider('substeps', 'substeps', (v) => String(v));
560
+ slider('iterations', 'iterations', (v) => String(v));
561
+
562
+ // Both band controls are logarithmic: the interesting range spans decades.
563
+ const floor_input = element('k_floor');
564
+ const floor_output = element('k_floor_out');
565
+
566
+ floor_input.value = String(Math.log2(control.k_floor));
567
+ floor_output.textContent = `1/${Math.round(1 / control.k_floor)}`;
568
+
569
+ floor_input.addEventListener('input', () => {
570
+ control.k_floor = Math.pow(2, Number(floor_input.value));
571
+
572
+ floor_output.textContent = control.k_floor >= 1
573
+ ? control.k_floor.toFixed(2)
574
+ : `1/${(1 / control.k_floor).toFixed(1)}`;
575
+ });
576
+
577
+ const beta_input = element('beta');
578
+ const beta_output = element('beta_out');
579
+
580
+ beta_input.value = String(Math.log10(control.beta));
581
+ beta_output.textContent = control.beta.toExponential(0);
582
+
583
+ beta_input.addEventListener('input', () => {
584
+ control.beta = Math.pow(10, Number(beta_input.value));
585
+
586
+ beta_output.textContent = control.beta.toExponential(0);
587
+ });
588
+
589
+ element('defaults').addEventListener('click', () => {
590
+ control.k_floor = 1 / 32;
591
+ control.beta = CLOTH_SOLVER_TUNING.beta;
592
+
593
+ floor_input.value = String(Math.log2(control.k_floor));
594
+ floor_output.textContent = '1/32';
595
+
596
+ beta_input.value = String(Math.log10(control.beta));
597
+ beta_output.textContent = control.beta.toExponential(0);
598
+ });
599
+
600
+ const sweep_button = element('sweep');
601
+ const unswept_button = element('unswept');
602
+
603
+ sweep_button.addEventListener('click', () => {
604
+ control.swept = true;
605
+
606
+ sweep_button.classList.add('on');
607
+ unswept_button.classList.remove('on');
608
+ });
609
+
610
+ unswept_button.addEventListener('click', () => {
611
+ control.swept = false;
612
+
613
+ sweep_button.classList.remove('on');
614
+ unswept_button.classList.add('on');
615
+ });
616
+
617
+ element('pause').addEventListener('click', (event) => {
618
+ control.running = !control.running;
619
+
620
+ event.target.textContent = control.running ? 'pause' : 'resume';
621
+ });
622
+
623
+ element('single').addEventListener('click', step_once);
624
+ element('reset').addEventListener('click', rebuild);
625
+
626
+ element('animate').addEventListener('click', (event) => {
627
+ control.animate = !control.animate;
628
+
629
+ event.target.classList.toggle('on', control.animate);
630
+ });
631
+
632
+ // ----------------------------------------------------------------- camera
633
+
634
+ let drag = null;
635
+
636
+ canvas.addEventListener('pointerdown', (event) => {
637
+ canvas.setPointerCapture(event.pointerId);
638
+ canvas.classList.add('grabbing');
639
+
640
+ drag = {
641
+ x: event.clientX,
642
+ y: event.clientY,
643
+ pan: event.shiftKey || event.button === 2
644
+ };
645
+ });
646
+
647
+ canvas.addEventListener('pointermove', (event) => {
648
+ if (drag === null) {
649
+ return;
650
+ }
651
+
652
+ const dx = event.clientX - drag.x;
653
+ const dy = event.clientY - drag.y;
654
+
655
+ drag.x = event.clientX;
656
+ drag.y = event.clientY;
657
+
658
+ if (!drag.pan) {
659
+ camera.yaw -= dx * 0.006;
660
+ camera.pitch = Math.max(-1.4, Math.min(1.4, camera.pitch + dy * 0.006));
661
+
662
+ return;
663
+ }
664
+
665
+ const scale = camera.distance * 0.0016;
666
+
667
+ camera.target[0] -= dx * Math.cos(camera.yaw) * scale;
668
+ camera.target[2] += dx * Math.sin(camera.yaw) * scale;
669
+ camera.target[1] += dy * scale;
670
+ });
671
+
672
+ function release(event) {
673
+ drag = null;
674
+
675
+ canvas.classList.remove('grabbing');
676
+
677
+ if (event !== undefined && canvas.hasPointerCapture(event.pointerId)) {
678
+ canvas.releasePointerCapture(event.pointerId);
679
+ }
680
+ }
681
+
682
+ canvas.addEventListener('pointerup', release);
683
+ canvas.addEventListener('pointercancel', release);
684
+ canvas.addEventListener('contextmenu', (event) => event.preventDefault());
685
+
686
+ canvas.addEventListener('wheel', (event) => {
687
+ event.preventDefault();
688
+
689
+ camera.distance = Math.max(0.1, Math.min(20, camera.distance * Math.exp(event.deltaY * 0.0012)));
690
+ }, { passive: false });
691
+
692
+ // ------------------------------------------------------------------ start
693
+
694
+ function resize() {
695
+ const ratio = Math.min(2, window.devicePixelRatio || 1);
696
+
697
+ canvas.width = Math.round(canvas.clientWidth * ratio);
698
+ canvas.height = Math.round(canvas.clientHeight * ratio);
699
+ }
700
+
701
+ window.addEventListener('resize', resize);
702
+
703
+ resize();
704
+ rebuild();
705
+
706
+ requestAnimationFrame(frame);
707
+
708
+ // A handle for a driver script — `requestAnimationFrame` does not fire while
709
+ // the pane is hidden, so a driver steps the page itself.
710
+ globalThis.cloth_collide_playground = {
711
+ control,
712
+ camera,
713
+ rebuild,
714
+ step_once,
715
+ penetration,
716
+ get cloth() { return cloth; },
717
+ get colliders() { return colliders; },
718
+ get contacts() { return contact_rows; },
719
+ slots: CLOTH_CONTACT_SLOTS
720
+ };