@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
@@ -1,645 +1,317 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import { quat3_multiply } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
3
- import { v3_quaternion_apply } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
4
- import { TransformAttachment } from "../../../ecs/transform-attachment/TransformAttachment.js";
5
- import {
6
- TRANSFORM64_ROTATION_OFFSET,
7
- TRANSFORM64_TRANSLATION_OFFSET,
8
- Transform64
9
- } from "../../../ecs/transform/Transform64.js";
10
- import { ClothState } from "../solver/ClothState.js";
11
- import { Cloth } from "./Cloth.js";
12
- import { ClothDynamicsFlags } from "./ClothDynamicsFlags.js";
13
- import { ClothExclude } from "./ClothExclude.js";
14
- import { ClothInstance } from "./ClothInstance.js";
15
- import { cloth_bend_ratio, cloth_strain_allowance } from "./cloth_dynamics_map.js";
16
-
17
- /**
18
- * # Seeding a cloth from an entity subtree — §11.1
19
- *
20
- * One particle per entity in the transform subtree of the entity the
21
- * {@link Cloth} is on, walked breadth-first through
22
- * `TransformAttachmentSystem#findChildrenOf`, stopping at a nested `Cloth` or a
23
- * {@link ClothExclude}. Stretch rows along the parent-child links, a bending
24
- * model along each chain, and the anchor itself as the fixed set — so `d_geo`
25
- * is distance along the chain from the anchor and every derived quantity in
26
- * §3.4 works unchanged.
27
- *
28
- * ## What "bend rows along each chain" turned out to mean
29
- *
30
- * §11.1 asks for bend rows and the row model has one — the quadratic hinge of
31
- * §6, four particles across the diamond either side of a shared edge. **A chain
32
- * has no diamond.** `bt_edge_read_diamond` declines a wire edge for exactly
33
- * that reason, and it is right to: the stencil is a discrete mean curvature over
34
- * a surface and a rope is not one.
35
- *
36
- * The bending model here is instead a **skip row** — an ordinary stretch row
37
- * between a node and its grandparent, at whatever distance the authored pose
38
- * puts them. It is §6's own doctrine applied once more ("one row model,
39
- * parameterised by mode"), it costs no new row kind, and it has the two
40
- * properties §6 wanted from quadratic bending: there is no normal, no angle and
41
- * no sign, so nothing degenerates at 0° or 180°, and a fold is high energy
42
- * rather than a singular configuration. It also does something the quadratic
43
- * stencil explicitly cannot (§18): because its rest length is read from the
44
- * authored pose rather than assumed flat, **it holds a curved rest shape**.
45
- *
46
- * The row is soft, so its stiffness has to stay inside the dissipative band
47
- * M0 measured; `cloth_dynamics_map.js` is where that is arranged and why.
48
- *
49
- * ## Mass is uniform, and the answer does not depend on it
50
- *
51
- * {@link ClothDynamics} carries no mass or density, and it does not need to.
52
- * Every row scale in this system is a **ratio** of the row's own `m/h²` — the
53
- * hard rows through the penalty band (`MEASUREMENTS.md` §4 held a chain at a
54
- * fixed ratio across five decades of particle mass and got the same residual
55
- * column every time), the soft ones through `cloth_bend_ratio` — and gravity is
56
- * an acceleration. So the constant below is arbitrary in the strict sense that
57
- * changing it changes no trajectory, and what matters about it is only that it
58
- * is the same for every particle.
59
- *
60
- * @author Alex Goldring
61
- * @copyright Company Named Limited (c) 2026
62
- */
63
-
64
- /**
65
- * Mass of one particle, kg. Arbitrary and uniform — see the module docblock for
66
- * why "arbitrary" is a statement about the solver rather than a shrug.
67
- * @type {number}
68
- */
69
- export const CLOTH_PARTICLE_MASS = 0.02;
70
-
71
- /**
72
- * Slack radius at `ClothDynamics#slack = 1`, as a multiple of `d_geo`.
73
- *
74
- * It is **2**, and that is a derivation rather than a taste. The tether row
75
- * already holds every particle within `d_geo` of the anchor, and the animated
76
- * position it is leashed to is itself within `d_geo` of the anchor — a chord is
77
- * never longer than the arc it subtends. By the triangle inequality the two can
78
- * therefore never be more than `2·d_geo` apart, so a slack radius of `2·d_geo`
79
- * is **provably inactive** and `slack = 1` means exactly "no leash" with no
80
- * sentinel value and no discontinuity to code around.
81
- *
82
- * §3.4 writes `r_i = slack · d_geo,i`, which makes `slack = 1` a real and
83
- * fairly tight constraint instead: a rope hanging straight down cannot swing
84
- * through 90°, because its tip would move `√2·d_geo` to get there. That is a
85
- * bound nobody asked for at the setting that is supposed to ask for none.
86
- * @type {number}
87
- */
88
- export const CLOTH_SLACK_REACH = 2;
89
-
90
- /**
91
- * Walk the subtree and build everything {@link ClothSystem} needs to step it.
92
- *
93
- * @param {ClothInstance} instance seeded in place
94
- * @param {EntityComponentDataset} ecd
95
- * @param {TransformAttachmentSystem} attachments
96
- * @param {number} dt fixed step, seconds — the bend row's scale is a ratio of
97
- * `m/dt²`, so the step length is part of the rest data
98
- * @returns {void}
99
- */
100
- export function cloth_seed_subtree(instance, ecd, attachments, dt) {
101
- const remembered = remember_rest_pose(instance);
102
-
103
- const anchor = instance.entity;
104
-
105
- const node_entity = [anchor];
106
- const node_parent = [-1];
107
-
108
- const pruned_entity = [];
109
- const pruned_parent = [];
110
-
111
- const scratch = [];
112
-
113
- // Breadth-first, so a node's parent is always already numbered and the
114
- // write-back can walk forwards.
115
- for (let node = 0; node < node_entity.length; node++) {
116
- const entity = node_entity[node];
117
-
118
- const child_count = attachments.countChildrenOf(entity);
119
-
120
- if (child_count === 0) {
121
- continue;
122
- }
123
-
124
- scratch.length = child_count;
125
-
126
- attachments.findChildrenOf(scratch, 0, entity);
127
-
128
- for (let i = 0; i < child_count; i++) {
129
- const child = scratch[i];
130
-
131
- const excluded = ecd.getComponent(child, ClothExclude) !== undefined
132
- || ecd.getComponent(child, Cloth) !== undefined;
133
-
134
- if (excluded) {
135
- pruned_entity.push(child);
136
- pruned_parent.push(node);
137
-
138
- continue;
139
- }
140
-
141
- if (ecd.getComponent(child, Transform64) === undefined) {
142
- // Not in the transform hierarchy in any usable sense. Its own
143
- // children are unreachable too, so it is a prune rather than a
144
- // skip — and it is not posed by the write-back either, because
145
- // there is nothing to pose.
146
- continue;
147
- }
148
-
149
- node_entity.push(child);
150
- node_parent.push(node);
151
- }
152
- }
153
-
154
- const node_count = node_entity.length;
155
-
156
- instance.node_count = node_count;
157
- instance.node_entity = Int32Array.from(node_entity);
158
- instance.node_parent = Int32Array.from(node_parent);
159
-
160
- instance.pruned_count = pruned_entity.length;
161
- instance.pruned_entity = Int32Array.from(pruned_entity);
162
- instance.pruned_parent = Int32Array.from(pruned_parent);
163
-
164
- instance.pruned_transform = new Array(instance.pruned_count);
165
- instance.pruned_attachment = new Array(instance.pruned_count);
166
-
167
- for (let i = 0; i < instance.pruned_count; i++) {
168
- const entity = instance.pruned_entity[i];
169
-
170
- instance.pruned_transform[i] = ecd.getComponent(entity, Transform64);
171
- instance.pruned_attachment[i] = ecd.getComponent(entity, TransformAttachment);
172
- }
173
-
174
- instance.node_transform = new Array(node_count);
175
- instance.node_attachment = new Array(node_count);
176
-
177
- for (let node = 0; node < node_count; node++) {
178
- const entity = instance.node_entity[node];
179
-
180
- const transform = ecd.getComponent(entity, Transform64);
181
-
182
- assert.defined(transform, `Transform64 of cloth node ${node} (entity ${entity})`);
183
-
184
- instance.node_transform[node] = transform;
185
- instance.node_attachment[node] = node === 0
186
- ? null
187
- : ecd.getComponent(entity, TransformAttachment);
188
- }
189
-
190
- build_children(instance);
191
- read_rest_pose(instance, remembered);
192
-
193
- instance.dynamics_seen.copy(instance.cloth.dynamics);
194
-
195
- build_state(instance, dt);
196
- }
197
-
198
- /**
199
- * CSR children per node, from the parent array.
200
- * @param {ClothInstance} instance
201
- * @private
202
- */
203
- function build_children(instance) {
204
- const node_count = instance.node_count;
205
- const parent = instance.node_parent;
206
-
207
- const offset = new Int32Array(node_count + 1);
208
-
209
- for (let node = 1; node < node_count; node++) {
210
- offset[parent[node] + 1]++;
211
- }
212
-
213
- for (let node = 0; node < node_count; node++) {
214
- offset[node + 1] += offset[node];
215
- }
216
-
217
- const child = new Int32Array(node_count > 0 ? node_count - 1 : 0);
218
- const cursor = offset.slice(0, node_count);
219
-
220
- for (let node = 1; node < node_count; node++) {
221
- child[cursor[parent[node]]++] = node;
222
- }
223
-
224
- instance.node_child_offset = offset;
225
- instance.node_child = child;
226
- }
227
-
228
- /**
229
- * Capture the authored pose, and compose it into the anchor's frame.
230
- *
231
- * The **local** transforms are the source, not the world ones. That is not a
232
- * preference: the write-back takes ownership of `TransformAttachment#transform`
233
- * (§11.1), so this is the one moment the authored pose is still readable, and
234
- * reading the world transforms instead would give the same answer now and the
235
- * *simulated* pose on any later re-seed.
236
- *
237
- * `remembered` carries a previous seeding's capture across a re-seed, keyed by
238
- * entity, so a structural change to a live cloth keeps the authored pose of
239
- * every node that was already in it.
240
- *
241
- * The composed result is constant — the subtree's authored pose is rigid
242
- * relative to its anchor, so in the anchor's own frame it does not move — which
243
- * is why nothing recomposes it per step.
244
- *
245
- * @param {ClothInstance} instance
246
- * @param {Map<number, {position: number[], rotation: number[]}>} remembered
247
- * @private
248
- */
249
- function read_rest_pose(instance, remembered) {
250
- const node_count = instance.node_count;
251
-
252
- const local_position = new Float32Array(node_count * 3);
253
- const local_rotation = new Float32Array(node_count * 4);
254
-
255
- const position = new Float32Array(node_count * 3);
256
- const rotation = new Float32Array(node_count * 4);
257
-
258
- // The anchor is the origin of the frame it anchors: no offset, no rotation.
259
- local_rotation[3] = 1;
260
- rotation[3] = 1;
261
-
262
- const parent = instance.node_parent;
263
-
264
- for (let node = 1; node < node_count; node++) {
265
- const captured = remembered.get(instance.node_entity[node]);
266
-
267
- if (captured !== undefined) {
268
- local_position[node * 3] = captured.position[0];
269
- local_position[node * 3 + 1] = captured.position[1];
270
- local_position[node * 3 + 2] = captured.position[2];
271
-
272
- local_rotation[node * 4] = captured.rotation[0];
273
- local_rotation[node * 4 + 1] = captured.rotation[1];
274
- local_rotation[node * 4 + 2] = captured.rotation[2];
275
- local_rotation[node * 4 + 3] = captured.rotation[3];
276
- } else {
277
- const attachment = instance.node_attachment[node];
278
-
279
- const local = attachment === undefined || attachment === null
280
- ? null
281
- : attachment.transform;
282
-
283
- if (local === null) {
284
- // In the walk because something said it was a child, but with no
285
- // local transform to compose. Its authored pose is its parent's,
286
- // which is the only answer available and is at least inside the
287
- // reachable set.
288
- local_rotation[node * 4 + 3] = 1;
289
- } else {
290
- local_position[node * 3] = local[TRANSFORM64_TRANSLATION_OFFSET];
291
- local_position[node * 3 + 1] = local[TRANSFORM64_TRANSLATION_OFFSET + 1];
292
- local_position[node * 3 + 2] = local[TRANSFORM64_TRANSLATION_OFFSET + 2];
293
-
294
- local_rotation[node * 4] = local[TRANSFORM64_ROTATION_OFFSET];
295
- local_rotation[node * 4 + 1] = local[TRANSFORM64_ROTATION_OFFSET + 1];
296
- local_rotation[node * 4 + 2] = local[TRANSFORM64_ROTATION_OFFSET + 2];
297
- local_rotation[node * 4 + 3] = local[TRANSFORM64_ROTATION_OFFSET + 3];
298
- }
299
- }
300
-
301
- // Compose down the walk. Breadth-first numbering means the parent is
302
- // always already done, so one forward pass is the whole hierarchy.
303
- const p = parent[node];
304
-
305
- const pqx = rotation[p * 4];
306
- const pqy = rotation[p * 4 + 1];
307
- const pqz = rotation[p * 4 + 2];
308
- const pqw = rotation[p * 4 + 3];
309
-
310
- v3_quaternion_apply(
311
- COMPOSE, 0,
312
- local_position[node * 3], local_position[node * 3 + 1], local_position[node * 3 + 2],
313
- pqx, pqy, pqz, pqw
314
- );
315
-
316
- position[node * 3] = position[p * 3] + COMPOSE[0];
317
- position[node * 3 + 1] = position[p * 3 + 1] + COMPOSE[1];
318
- position[node * 3 + 2] = position[p * 3 + 2] + COMPOSE[2];
319
-
320
- quat3_multiply(
321
- rotation, node * 4,
322
- pqx, pqy, pqz, pqw,
323
- local_rotation[node * 4], local_rotation[node * 4 + 1],
324
- local_rotation[node * 4 + 2], local_rotation[node * 4 + 3]
325
- );
326
- }
327
-
328
- instance.rest_local_position = local_position;
329
- instance.rest_local_rotation = local_rotation;
330
-
331
- instance.animated_position = position;
332
- instance.animated_rotation = rotation;
333
- }
334
-
335
- /**
336
- * @type {Float64Array}
337
- * @private
338
- */
339
- const COMPOSE = new Float64Array(3);
340
-
341
- /**
342
- * The authored pose a previous seeding captured, keyed by entity, so a re-seed
343
- * of a live cloth does not mistake the shape the solver left behind for the
344
- * shape the author authored.
345
- *
346
- * @param {ClothInstance} instance
347
- * @returns {Map<number, {position: number[], rotation: number[]}>}
348
- * @private
349
- */
350
- function remember_rest_pose(instance) {
351
- const remembered = new Map();
352
-
353
- if (!instance.seeded || instance.rest_local_position === null) {
354
- return remembered;
355
- }
356
-
357
- for (let node = 1; node < instance.node_count; node++) {
358
- remembered.set(instance.node_entity[node], {
359
- position: [
360
- instance.rest_local_position[node * 3],
361
- instance.rest_local_position[node * 3 + 1],
362
- instance.rest_local_position[node * 3 + 2]
363
- ],
364
- rotation: [
365
- instance.rest_local_rotation[node * 4],
366
- instance.rest_local_rotation[node * 4 + 1],
367
- instance.rest_local_rotation[node * 4 + 2],
368
- instance.rest_local_rotation[node * 4 + 3]
369
- ]
370
- });
371
- }
372
-
373
- return remembered;
374
- }
375
-
376
- /**
377
- * Rows, geodesics and the `ClothState` itself.
378
- *
379
- * @param {ClothInstance} instance
380
- * @param {number} dt
381
- * @private
382
- */
383
- function build_state(instance, dt) {
384
- const node_count = instance.node_count;
385
-
386
- const parent = instance.node_parent;
387
- const rest = instance.animated_position;
388
-
389
- const dynamics = instance.cloth.dynamics;
390
-
391
- const strain_limited = (dynamics.flags & ClothDynamicsFlags.StrainLimit) !== 0;
392
-
393
- const geodesic = new Float32Array(node_count);
394
-
395
- const mass_inverse = new Float32Array(node_count);
396
-
397
- // The anchor is the fixed set: zero inverse mass, and a geodesic of zero,
398
- // which is what makes every derived radius collapse onto it correctly.
399
- for (let node = 1; node < node_count; node++) {
400
- mass_inverse[node] = 1 / CLOTH_PARTICLE_MASS;
401
- }
402
-
403
- const stretch_particle = [];
404
- const stretch_rest = [];
405
- const stretch_stiffness = [];
406
- const stretch_force_min = [];
407
- const stretch_force_max = [];
408
-
409
- const link_row = new Int32Array(node_count).fill(-1);
410
- const strain_row = new Int32Array(node_count).fill(-1);
411
- const skip_row = new Int32Array(node_count).fill(-1);
412
-
413
- const strain = cloth_strain_allowance(dynamics.stretch);
414
-
415
- // A skip row spans two links and both of its ends are free, so its
416
- // effective inertia along the constraint is half a particle's.
417
- const bend_stiffness = cloth_bend_ratio(dynamics.bend)
418
- * (CLOTH_PARTICLE_MASS * 0.5) / (dt * dt);
419
-
420
- let reach = 0;
421
-
422
- for (let node = 1; node < node_count; node++) {
423
- const p = parent[node];
424
-
425
- const length = Math.hypot(
426
- rest[node * 3] - rest[p * 3],
427
- rest[node * 3 + 1] - rest[p * 3 + 1],
428
- rest[node * 3 + 2] - rest[p * 3 + 2]
429
- );
430
-
431
- geodesic[node] = geodesic[p] + length;
432
-
433
- if (geodesic[node] > reach) {
434
- reach = geodesic[node];
435
- }
436
-
437
- link_row[node] = stretch_rest.length;
438
-
439
- // Hard, bilateral: the fabric row. It is hard because a *penalty* stiff
440
- // enough to be fabric adds energy rather than dissipating it — see
441
- // `cloth_dynamics_map.js`, and `MEASUREMENTS.md` §6 for the +6.8 J.
442
- stretch_particle.push(p, node);
443
- stretch_rest.push(length);
444
- stretch_stiffness.push(Infinity);
445
- stretch_force_min.push(-Infinity);
446
- stretch_force_max.push(Infinity);
447
-
448
- if (strain_limited) {
449
- strain_row[node] = stretch_rest.length;
450
-
451
- // Hard, one-sided: §6's strain limit, and note the clamp. A row
452
- // that bounds a distance from ABOVE may only apply force that
453
- // reduces `C`, which for a distance row is the positive half —
454
- // `[0, +∞)`. §6's table has this row at `(−∞, 0]`, which is the
455
- // force that pushes the two ends apart; `cloth_leash_row.js`
456
- // carries the derivation and a spec that shows it directly.
457
- stretch_particle.push(p, node);
458
- stretch_rest.push(length * (1 + strain));
459
- stretch_stiffness.push(Infinity);
460
- stretch_force_min.push(0);
461
- stretch_force_max.push(Infinity);
462
- }
463
-
464
- const grandparent = parent[p];
465
-
466
- if (grandparent >= 0 && bend_stiffness > 0) {
467
- const span = Math.hypot(
468
- rest[node * 3] - rest[grandparent * 3],
469
- rest[node * 3 + 1] - rest[grandparent * 3 + 1],
470
- rest[node * 3 + 2] - rest[grandparent * 3 + 2]
471
- );
472
-
473
- skip_row[node] = stretch_rest.length;
474
-
475
- // Soft, bilateral: the bending model. Its rest length is read from
476
- // the authored pose, so a chain authored with a curve holds it.
477
- stretch_particle.push(grandparent, node);
478
- stretch_rest.push(span);
479
- stretch_stiffness.push(bend_stiffness);
480
- stretch_force_min.push(-Infinity);
481
- stretch_force_max.push(Infinity);
482
- }
483
- }
484
-
485
- const leash_particle = [];
486
- const leash_radius = [];
487
-
488
- const slack_row = new Int32Array(node_count).fill(-1);
489
- const tether_row = new Int32Array(node_count).fill(-1);
490
-
491
- const slack_scale = CLOTH_SLACK_REACH * clamp01(dynamics.slack);
492
-
493
- for (let node = 1; node < node_count; node++) {
494
- // Tether first: §7 P2's long-range attachment, and the row that makes a
495
- // low iteration budget viable at all, because being hard it is enforced
496
- // almost exactly in one iteration however deep the chain is.
497
- tether_row[node] = leash_radius.length;
498
-
499
- leash_particle.push(node);
500
- leash_radius.push(geodesic[node]);
501
-
502
- if (slack_scale < CLOTH_SLACK_REACH) {
503
- slack_row[node] = leash_radius.length;
504
-
505
- leash_particle.push(node);
506
- leash_radius.push(slack_scale * geodesic[node]);
507
- }
508
- }
509
-
510
- let shortest_row = Infinity;
511
-
512
- for (let row = 0; row < stretch_rest.length; row++) {
513
- if (stretch_rest[row] > 0 && stretch_rest[row] < shortest_row) {
514
- shortest_row = stretch_rest[row];
515
- }
516
- }
517
-
518
- // A cloth of one particle has no rows at all, so it has no correction
519
- // authority to speak of and its reach is the only length it carries.
520
- instance.shortest_row = shortest_row < Infinity ? shortest_row : reach;
521
-
522
- instance.geodesic = geodesic;
523
- instance.reach = reach;
524
- instance.link_row = link_row;
525
- instance.strain_row = strain_row;
526
- instance.skip_row = skip_row;
527
- instance.slack_row = slack_row;
528
- instance.tether_row = tether_row;
529
-
530
- const state = ClothState.from({
531
- position: rest,
532
- mass_inverse,
533
- stretch_particle: Int32Array.from(stretch_particle),
534
- stretch_rest: Float32Array.from(stretch_rest),
535
- stretch_stiffness: Float32Array.from(stretch_stiffness),
536
- stretch_force_min: Float32Array.from(stretch_force_min),
537
- stretch_force_max: Float32Array.from(stretch_force_max),
538
- leash_particle: Int32Array.from(leash_particle),
539
- leash_radius: Float32Array.from(leash_radius)
540
- });
541
-
542
- const particle_of_node = new Int32Array(node_count);
543
-
544
- for (let particle = 0; particle < node_count; particle++) {
545
- particle_of_node[state.source_of_particle[particle]] = particle;
546
- }
547
-
548
- instance.particle_of_node = particle_of_node;
549
- instance.blended_position = new Float64Array(node_count * 3);
550
- instance.blended_rotation = new Float64Array(node_count * 4);
551
- instance.state = state;
552
- instance.seeded = true;
553
- }
554
-
555
- /**
556
- * @param {number} v
557
- * @returns {number}
558
- * @private
559
- */
560
- function clamp01(v) {
561
- return v < 0 ? 0 : (v > 1 ? 1 : v);
562
- }
563
-
564
- /**
565
- * Re-parameterise the rows of an already-seeded cloth from its current
566
- * {@link ClothDynamics}, without walking the subtree again.
567
- *
568
- * This is what makes `cloth.dynamics.slack = 0.2` work live: the topology, the
569
- * rest lengths and the geodesics are properties of the authored pose and do not
570
- * move, and everything that does move is a number in a row table.
571
- *
572
- * The multipliers are **not** reset. A row whose radius has just changed is
573
- * carrying a force appropriate to the radius it had, and it is one warm start
574
- * away from carrying the right one; zeroing them would put a visible jolt into
575
- * every parameter change instead.
576
- *
577
- * @param {ClothInstance} instance
578
- * @param {number} dt
579
- * @returns {void}
580
- */
581
- export function cloth_reparameterise(instance, dt) {
582
- const dynamics = instance.cloth.dynamics;
583
-
584
- const state = instance.state;
585
-
586
- const node_count = instance.node_count;
587
-
588
- const strain = cloth_strain_allowance(dynamics.stretch);
589
-
590
- const bend_stiffness = cloth_bend_ratio(dynamics.bend)
591
- * (CLOTH_PARTICLE_MASS * 0.5) / (dt * dt);
592
-
593
- const slack_scale = CLOTH_SLACK_REACH * clamp01(dynamics.slack);
594
-
595
- for (let node = 1; node < node_count; node++) {
596
- const link = instance.link_row[node];
597
- const strain_limit = instance.strain_row[node];
598
- const skip = instance.skip_row[node];
599
- const slack = instance.slack_row[node];
600
-
601
- if (strain_limit >= 0) {
602
- state.stretch_rest[strain_limit] = state.stretch_rest[link] * (1 + strain);
603
- }
604
-
605
- if (skip >= 0) {
606
- state.stretch_stiffness[skip] = bend_stiffness;
607
- }
608
-
609
- if (slack >= 0) {
610
- state.leash_radius[slack] = slack_scale * instance.geodesic[node];
611
- }
612
- }
613
-
614
- instance.dynamics_seen.copy(dynamics);
615
- }
616
-
617
- /**
618
- * Whether a dynamics change needs the subtree walked again rather than merely
619
- * re-parameterised.
620
- *
621
- * Two fields do: turning the strain limit on or off adds or removes rows, and
622
- * so does moving `slack` across the bound at which its row is provably
623
- * inactive. Everything else is a number in a table that already exists.
624
- *
625
- * @param {ClothDynamics} before
626
- * @param {ClothDynamics} after
627
- * @returns {boolean}
628
- */
629
- export function cloth_dynamics_change_needs_reseed(before, after) {
630
- const strain_before = (before.flags & ClothDynamicsFlags.StrainLimit) !== 0;
631
- const strain_after = (after.flags & ClothDynamicsFlags.StrainLimit) !== 0;
632
-
633
- if (strain_before !== strain_after) {
634
- return true;
635
- }
636
-
637
- const leashed_before = clamp01(before.slack) < 1;
638
- const leashed_after = clamp01(after.slack) < 1;
639
-
640
- if (leashed_before !== leashed_after) {
641
- return true;
642
- }
643
-
644
- return (before.bend > 0) !== (after.bend > 0);
645
- }
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { quat3_multiply } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
3
+ import { v3_quaternion_apply } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
4
+ import { TransformAttachment } from "../../../ecs/transform-attachment/TransformAttachment.js";
5
+ import {
6
+ TRANSFORM64_ROTATION_OFFSET,
7
+ TRANSFORM64_TRANSLATION_OFFSET,
8
+ Transform64
9
+ } from "../../../ecs/transform/Transform64.js";
10
+ import { Cloth } from "./Cloth.js";
11
+ import { ClothExclude } from "./ClothExclude.js";
12
+ import { ClothInstance } from "./ClothInstance.js";
13
+ import { cloth_build_children, cloth_build_rows } from "./cloth_build_rows.js";
14
+
15
+ /**
16
+ * # Seeding a cloth from an entity subtree — §11.1
17
+ *
18
+ * One particle per entity in the transform subtree of the entity the
19
+ * {@link Cloth} is on, walked breadth-first through
20
+ * `TransformAttachmentSystem#findChildrenOf`, stopping at a nested `Cloth` or a
21
+ * {@link ClothExclude}. Stretch rows along the parent-child links, a bending
22
+ * model along each chain, and the anchor itself as the fixed set — so `d_geo`
23
+ * is distance along the chain from the anchor and every derived quantity in
24
+ * §3.4 works unchanged.
25
+ *
26
+ * ## What "bend rows along each chain" turned out to mean
27
+ *
28
+ * §11.1 asks for bend rows and the row model has one — the quadratic hinge of
29
+ * §6, four particles across the diamond either side of a shared edge. **A chain
30
+ * has no diamond.** `bt_edge_read_diamond` declines a wire edge for exactly
31
+ * that reason, and it is right to: the stencil is a discrete mean curvature over
32
+ * a surface and a rope is not one.
33
+ *
34
+ * The bending model here is instead a **skip row** — an ordinary stretch row
35
+ * between a node and its grandparent, at whatever distance the authored pose
36
+ * puts them. It is §6's own doctrine applied once more ("one row model,
37
+ * parameterised by mode"), it costs no new row kind, and it has the two
38
+ * properties §6 wanted from quadratic bending: there is no normal, no angle and
39
+ * no sign, so nothing degenerates at 0° or 180°, and a fold is high energy
40
+ * rather than a singular configuration. It also does something the quadratic
41
+ * stencil explicitly cannot (§18): because its rest length is read from the
42
+ * authored pose rather than assumed flat, **it holds a curved rest shape**.
43
+ *
44
+ * The row is soft, so its stiffness has to stay inside the dissipative band
45
+ * M0 measured; `cloth_dynamics_map.js` is where that is arranged and why.
46
+ *
47
+ * ## Mass is uniform, and the answer does not depend on it
48
+ *
49
+ * {@link ClothDynamics} carries no mass or density, and it does not need to.
50
+ * Every row scale in this system is a **ratio** of the row's own `m/h²` — the
51
+ * hard rows through the penalty band (`MEASUREMENTS.md` §4 held a chain at a
52
+ * fixed ratio across five decades of particle mass and got the same residual
53
+ * column every time), the soft ones through `cloth_bend_ratio` — and gravity is
54
+ * an acceleration. So the constant below is arbitrary in the strict sense that
55
+ * changing it changes no trajectory, and what matters about it is only that it
56
+ * is the same for every particle.
57
+ *
58
+ * @author Alex Goldring
59
+ * @copyright Company Named Limited (c) 2026
60
+ */
61
+
62
+ /**
63
+ * Walk the subtree and build everything {@link ClothSystem} needs to step it.
64
+ *
65
+ * @param {ClothInstance} instance seeded in place
66
+ * @param {EntityComponentDataset} ecd
67
+ * @param {TransformAttachmentSystem} attachments
68
+ * @param {number} dt fixed step, seconds — the bend row's scale is a ratio of
69
+ * `m/dt²`, so the step length is part of the rest data
70
+ * @returns {void}
71
+ */
72
+ export function cloth_seed_subtree(instance, ecd, attachments, dt) {
73
+ const remembered = remember_rest_pose(instance);
74
+
75
+ const anchor = instance.entity;
76
+
77
+ const node_entity = [anchor];
78
+ const node_parent = [-1];
79
+
80
+ const pruned_entity = [];
81
+ const pruned_parent = [];
82
+
83
+ const scratch = [];
84
+
85
+ // Breadth-first, so a node's parent is always already numbered and the
86
+ // write-back can walk forwards.
87
+ for (let node = 0; node < node_entity.length; node++) {
88
+ const entity = node_entity[node];
89
+
90
+ const child_count = attachments.countChildrenOf(entity);
91
+
92
+ if (child_count === 0) {
93
+ continue;
94
+ }
95
+
96
+ scratch.length = child_count;
97
+
98
+ attachments.findChildrenOf(scratch, 0, entity);
99
+
100
+ for (let i = 0; i < child_count; i++) {
101
+ const child = scratch[i];
102
+
103
+ const excluded = ecd.getComponent(child, ClothExclude) !== undefined
104
+ || ecd.getComponent(child, Cloth) !== undefined;
105
+
106
+ if (excluded) {
107
+ pruned_entity.push(child);
108
+ pruned_parent.push(node);
109
+
110
+ continue;
111
+ }
112
+
113
+ if (ecd.getComponent(child, Transform64) === undefined) {
114
+ // Not in the transform hierarchy in any usable sense. Its own
115
+ // children are unreachable too, so it is a prune rather than a
116
+ // skip — and it is not posed by the write-back either, because
117
+ // there is nothing to pose.
118
+ continue;
119
+ }
120
+
121
+ node_entity.push(child);
122
+ node_parent.push(node);
123
+ }
124
+ }
125
+
126
+ const node_count = node_entity.length;
127
+
128
+ instance.node_count = node_count;
129
+ instance.node_entity = Int32Array.from(node_entity);
130
+ instance.node_parent = Int32Array.from(node_parent);
131
+
132
+ instance.pruned_count = pruned_entity.length;
133
+ instance.pruned_entity = Int32Array.from(pruned_entity);
134
+ instance.pruned_parent = Int32Array.from(pruned_parent);
135
+
136
+ instance.pruned_transform = new Array(instance.pruned_count);
137
+ instance.pruned_attachment = new Array(instance.pruned_count);
138
+
139
+ for (let i = 0; i < instance.pruned_count; i++) {
140
+ const entity = instance.pruned_entity[i];
141
+
142
+ instance.pruned_transform[i] = ecd.getComponent(entity, Transform64);
143
+ instance.pruned_attachment[i] = ecd.getComponent(entity, TransformAttachment);
144
+ }
145
+
146
+ instance.node_transform = new Array(node_count);
147
+ instance.node_attachment = new Array(node_count);
148
+
149
+ for (let node = 0; node < node_count; node++) {
150
+ const entity = instance.node_entity[node];
151
+
152
+ const transform = ecd.getComponent(entity, Transform64);
153
+
154
+ assert.defined(transform, `Transform64 of cloth node ${node} (entity ${entity})`);
155
+
156
+ instance.node_transform[node] = transform;
157
+ instance.node_attachment[node] = node === 0
158
+ ? null
159
+ : ecd.getComponent(entity, TransformAttachment);
160
+ }
161
+
162
+ cloth_build_children(instance);
163
+ read_rest_pose(instance, remembered);
164
+
165
+ instance.dynamics_seen.copy(instance.cloth.dynamics);
166
+
167
+ cloth_build_rows(instance, dt);
168
+ }
169
+
170
+ /**
171
+ * Capture the authored pose, and compose it into the anchor's frame.
172
+ *
173
+ * The **local** transforms are the source, not the world ones. That is not a
174
+ * preference: the write-back takes ownership of `TransformAttachment#transform`
175
+ * (§11.1), so this is the one moment the authored pose is still readable, and
176
+ * reading the world transforms instead would give the same answer now and the
177
+ * *simulated* pose on any later re-seed.
178
+ *
179
+ * `remembered` carries a previous seeding's capture across a re-seed, keyed by
180
+ * entity, so a structural change to a live cloth keeps the authored pose of
181
+ * every node that was already in it.
182
+ *
183
+ * The composed result is constant — the subtree's authored pose is rigid
184
+ * relative to its anchor, so in the anchor's own frame it does not move — which
185
+ * is why nothing recomposes it per step.
186
+ *
187
+ * @param {ClothInstance} instance
188
+ * @param {Map<number, {position: number[], rotation: number[]}>} remembered
189
+ * @private
190
+ */
191
+ function read_rest_pose(instance, remembered) {
192
+ const node_count = instance.node_count;
193
+
194
+ const local_position = new Float32Array(node_count * 3);
195
+ const local_rotation = new Float32Array(node_count * 4);
196
+
197
+ const position = new Float32Array(node_count * 3);
198
+ const rotation = new Float32Array(node_count * 4);
199
+
200
+ // The anchor is the origin of the frame it anchors: no offset, no rotation.
201
+ local_rotation[3] = 1;
202
+ rotation[3] = 1;
203
+
204
+ const parent = instance.node_parent;
205
+
206
+ for (let node = 1; node < node_count; node++) {
207
+ const captured = remembered.get(instance.node_entity[node]);
208
+
209
+ if (captured !== undefined) {
210
+ local_position[node * 3] = captured.position[0];
211
+ local_position[node * 3 + 1] = captured.position[1];
212
+ local_position[node * 3 + 2] = captured.position[2];
213
+
214
+ local_rotation[node * 4] = captured.rotation[0];
215
+ local_rotation[node * 4 + 1] = captured.rotation[1];
216
+ local_rotation[node * 4 + 2] = captured.rotation[2];
217
+ local_rotation[node * 4 + 3] = captured.rotation[3];
218
+ } else {
219
+ const attachment = instance.node_attachment[node];
220
+
221
+ const local = attachment === undefined || attachment === null
222
+ ? null
223
+ : attachment.transform;
224
+
225
+ if (local === null) {
226
+ // In the walk because something said it was a child, but with no
227
+ // local transform to compose. Its authored pose is its parent's,
228
+ // which is the only answer available and is at least inside the
229
+ // reachable set.
230
+ local_rotation[node * 4 + 3] = 1;
231
+ } else {
232
+ local_position[node * 3] = local[TRANSFORM64_TRANSLATION_OFFSET];
233
+ local_position[node * 3 + 1] = local[TRANSFORM64_TRANSLATION_OFFSET + 1];
234
+ local_position[node * 3 + 2] = local[TRANSFORM64_TRANSLATION_OFFSET + 2];
235
+
236
+ local_rotation[node * 4] = local[TRANSFORM64_ROTATION_OFFSET];
237
+ local_rotation[node * 4 + 1] = local[TRANSFORM64_ROTATION_OFFSET + 1];
238
+ local_rotation[node * 4 + 2] = local[TRANSFORM64_ROTATION_OFFSET + 2];
239
+ local_rotation[node * 4 + 3] = local[TRANSFORM64_ROTATION_OFFSET + 3];
240
+ }
241
+ }
242
+
243
+ // Compose down the walk. Breadth-first numbering means the parent is
244
+ // always already done, so one forward pass is the whole hierarchy.
245
+ const p = parent[node];
246
+
247
+ const pqx = rotation[p * 4];
248
+ const pqy = rotation[p * 4 + 1];
249
+ const pqz = rotation[p * 4 + 2];
250
+ const pqw = rotation[p * 4 + 3];
251
+
252
+ v3_quaternion_apply(
253
+ COMPOSE, 0,
254
+ local_position[node * 3], local_position[node * 3 + 1], local_position[node * 3 + 2],
255
+ pqx, pqy, pqz, pqw
256
+ );
257
+
258
+ position[node * 3] = position[p * 3] + COMPOSE[0];
259
+ position[node * 3 + 1] = position[p * 3 + 1] + COMPOSE[1];
260
+ position[node * 3 + 2] = position[p * 3 + 2] + COMPOSE[2];
261
+
262
+ quat3_multiply(
263
+ rotation, node * 4,
264
+ pqx, pqy, pqz, pqw,
265
+ local_rotation[node * 4], local_rotation[node * 4 + 1],
266
+ local_rotation[node * 4 + 2], local_rotation[node * 4 + 3]
267
+ );
268
+ }
269
+
270
+ instance.rest_local_position = local_position;
271
+ instance.rest_local_rotation = local_rotation;
272
+
273
+ instance.animated_position = position;
274
+ instance.animated_rotation = rotation;
275
+ }
276
+
277
+ /**
278
+ * @type {Float64Array}
279
+ * @private
280
+ */
281
+ const COMPOSE = new Float64Array(3);
282
+
283
+ /**
284
+ * The authored pose a previous seeding captured, keyed by entity, so a re-seed
285
+ * of a live cloth does not mistake the shape the solver left behind for the
286
+ * shape the author authored.
287
+ *
288
+ * @param {ClothInstance} instance
289
+ * @returns {Map<number, {position: number[], rotation: number[]}>}
290
+ * @private
291
+ */
292
+ function remember_rest_pose(instance) {
293
+ const remembered = new Map();
294
+
295
+ if (!instance.seeded || instance.rest_local_position === null) {
296
+ return remembered;
297
+ }
298
+
299
+ for (let node = 1; node < instance.node_count; node++) {
300
+ remembered.set(instance.node_entity[node], {
301
+ position: [
302
+ instance.rest_local_position[node * 3],
303
+ instance.rest_local_position[node * 3 + 1],
304
+ instance.rest_local_position[node * 3 + 2]
305
+ ],
306
+ rotation: [
307
+ instance.rest_local_rotation[node * 4],
308
+ instance.rest_local_rotation[node * 4 + 1],
309
+ instance.rest_local_rotation[node * 4 + 2],
310
+ instance.rest_local_rotation[node * 4 + 3]
311
+ ]
312
+ });
313
+ }
314
+
315
+ return remembered;
316
+ }
317
+