@woosh/meep-engine 3.42.0 → 3.44.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 (345) hide show
  1. package/editor/prototypeEditorShell.d.ts.map +1 -1
  2. package/editor/prototypeEditorShell.js +4 -2
  3. package/package.json +91 -96
  4. package/src/engine/Clock.d.ts +15 -0
  5. package/src/engine/Clock.d.ts.map +1 -1
  6. package/src/engine/Clock.js +29 -5
  7. package/src/engine/Engine.d.ts.map +1 -1
  8. package/src/engine/Engine.js +11 -19
  9. package/src/engine/graphics/ecs/mesh-v2/MeshSkin.d.ts +57 -0
  10. package/src/engine/graphics/ecs/mesh-v2/MeshSkin.d.ts.map +1 -0
  11. package/src/engine/graphics/ecs/mesh-v2/MeshSkin.js +84 -0
  12. package/src/engine/graphics3/AnimationGraphSystem.d.ts +23 -13
  13. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
  14. package/src/engine/graphics3/AnimationGraphSystem.js +61 -64
  15. package/src/engine/graphics3/AnimationSystem.d.ts +24 -15
  16. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
  17. package/src/engine/graphics3/AnimationSystem.js +67 -61
  18. package/src/engine/graphics3/MeshSkinSystem.d.ts +56 -0
  19. package/src/engine/graphics3/MeshSkinSystem.d.ts.map +1 -0
  20. package/src/engine/graphics3/MeshSkinSystem.js +317 -0
  21. package/src/engine/graphics3/MeshSystem.d.ts +15 -35
  22. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  23. package/src/engine/graphics3/MeshSystem.js +21 -298
  24. package/src/engine/graphics3/RowChains.d.ts +76 -0
  25. package/src/engine/graphics3/RowChains.d.ts.map +1 -0
  26. package/src/engine/graphics3/RowChains.js +347 -0
  27. package/src/engine/graphics3/ShadedGeometrySystem.d.ts +6 -0
  28. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  29. package/src/engine/graphics3/ShadedGeometrySystem.js +329 -309
  30. package/src/engine/graphics3/animation/AnimationClips.d.ts +56 -0
  31. package/src/engine/graphics3/animation/AnimationClips.d.ts.map +1 -0
  32. package/src/engine/graphics3/animation/AnimationClips.js +79 -0
  33. package/src/engine/graphics3/animation/ClipListPlayer.d.ts +13 -14
  34. package/src/engine/graphics3/animation/ClipListPlayer.d.ts.map +1 -1
  35. package/src/engine/graphics3/animation/ClipListPlayer.js +262 -263
  36. package/src/engine/graphics3/animation/write_clip_playback.d.ts +3 -3
  37. package/src/engine/graphics3/animation/write_clip_playback.js +4 -4
  38. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts +5 -0
  39. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
  40. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +80 -75
  41. package/src/engine/graphics3/prefab/PrefabInstance.d.ts +4 -24
  42. package/src/engine/graphics3/prefab/PrefabInstance.d.ts.map +1 -1
  43. package/src/engine/graphics3/prefab/PrefabInstance.js +60 -80
  44. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts +16 -3
  45. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts.map +1 -1
  46. package/src/engine/graphics3/prefab/prefab_instantiate.js +49 -12
  47. package/src/engine/graphics3/prefab/prefab_place.d.ts +31 -17
  48. package/src/engine/graphics3/prefab/prefab_place.d.ts.map +1 -1
  49. package/src/engine/graphics3/prefab/prefab_place.js +175 -130
  50. package/src/engine/graphics3/register_clips.d.ts +64 -0
  51. package/src/engine/graphics3/register_clips.d.ts.map +1 -0
  52. package/src/engine/graphics3/register_clips.js +114 -0
  53. package/src/engine/physics/cloth/MEASUREMENTS.md +795 -0
  54. package/src/engine/physics/cloth/PLAN.md +339 -9
  55. package/src/engine/physics/cloth/build/ClothProxy.d.ts +113 -61
  56. package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -1
  57. package/src/engine/physics/cloth/build/ClothProxy.js +142 -77
  58. package/src/engine/physics/cloth/build/cloth_bind_in_frame.d.ts +22 -0
  59. package/src/engine/physics/cloth/build/cloth_bind_in_frame.d.ts.map +1 -0
  60. package/src/engine/physics/cloth/build/cloth_bind_in_frame.js +75 -0
  61. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +43 -30
  62. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -1
  63. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +357 -284
  64. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.d.ts +62 -0
  65. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.d.ts.map +1 -0
  66. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.js +280 -0
  67. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +1 -1
  68. package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +1 -1
  69. package/src/engine/physics/cloth/ecs/Cloth.d.ts +17 -9
  70. package/src/engine/physics/cloth/ecs/Cloth.d.ts.map +1 -1
  71. package/src/engine/physics/cloth/ecs/Cloth.js +220 -212
  72. package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +6 -0
  73. package/src/engine/physics/cloth/ecs/ClothHold.d.ts +91 -0
  74. package/src/engine/physics/cloth/ecs/ClothHold.d.ts.map +1 -0
  75. package/src/engine/physics/cloth/ecs/ClothHold.js +103 -0
  76. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +254 -48
  77. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  78. package/src/engine/physics/cloth/ecs/ClothInstance.js +277 -47
  79. package/src/engine/physics/cloth/ecs/ClothPin.d.ts +73 -0
  80. package/src/engine/physics/cloth/ecs/ClothPin.d.ts.map +1 -0
  81. package/src/engine/physics/cloth/ecs/ClothPin.js +92 -0
  82. package/src/engine/physics/cloth/ecs/ClothPinSerializationAdapter.d.ts +36 -0
  83. package/src/engine/physics/cloth/ecs/ClothPinSerializationAdapter.d.ts.map +1 -0
  84. package/src/engine/physics/cloth/ecs/ClothPinSerializationAdapter.js +61 -0
  85. package/src/engine/physics/cloth/ecs/ClothRig.d.ts +23 -17
  86. package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -1
  87. package/src/engine/physics/cloth/ecs/ClothRig.js +29 -27
  88. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +12 -14
  89. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  90. package/src/engine/physics/cloth/ecs/ClothSystem.js +20 -15
  91. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +57 -36
  92. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  93. package/src/engine/physics/cloth/ecs/ClothWorld.js +816 -179
  94. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +12 -13
  95. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -1
  96. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +20 -14
  97. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +63 -11
  98. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  99. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +321 -84
  100. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +9 -7
  101. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  102. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +9 -7
  103. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +3 -3
  104. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +30 -30
  105. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts +37 -0
  106. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts.map +1 -0
  107. package/src/engine/physics/cloth/ecs/cloth_geodesic.js +142 -0
  108. package/src/engine/physics/cloth/ecs/cloth_holds.d.ts +97 -0
  109. package/src/engine/physics/cloth/ecs/cloth_holds.d.ts.map +1 -0
  110. package/src/engine/physics/cloth/ecs/cloth_holds.js +608 -0
  111. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +2 -2
  112. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -1
  113. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +160 -9
  114. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +17 -22
  115. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  116. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +55 -117
  117. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +21 -6
  118. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  119. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +44 -15
  120. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +5 -0
  121. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  122. package/src/engine/physics/cloth/ecs/cloth_write_back.js +60 -16
  123. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -1
  124. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +65 -58
  125. package/src/engine/physics/cloth/playground/collide_main.js +1 -1
  126. package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -1
  127. package/src/engine/physics/cloth/playground/garment_build.js +17 -5
  128. package/src/engine/physics/cloth/playground/garment_main.js +1 -1
  129. package/src/engine/physics/cloth/playground/rig_build.d.ts.map +1 -1
  130. package/src/engine/physics/cloth/playground/rig_build.js +302 -296
  131. package/src/engine/physics/cloth/solver/ClothState.d.ts +45 -7
  132. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  133. package/src/engine/physics/cloth/solver/ClothState.js +63 -7
  134. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +6 -1
  135. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
  136. package/src/engine/physics/cloth/solver/cloth_contact_find.js +7 -2
  137. package/src/engine/physics/cloth/solver/cloth_hold_line.d.ts +114 -0
  138. package/src/engine/physics/cloth/solver/cloth_hold_line.d.ts.map +1 -0
  139. package/src/engine/physics/cloth/solver/cloth_hold_line.js +362 -0
  140. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +6 -2
  141. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -1
  142. package/src/engine/physics/cloth/solver/cloth_self_find.js +13 -13
  143. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +24 -12
  144. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  145. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +24 -12
  146. package/src/engine/physics/cloth/solver/cloth_step.d.ts +16 -0
  147. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  148. package/src/engine/physics/cloth/solver/cloth_step.js +183 -20
  149. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +1 -0
  150. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -1
  151. package/src/engine/physics/cloth/solver/cloth_step_instance.js +10 -3
  152. package/src/engine/physics/cloth/solver/constraint/cloth_leash_row.d.ts +1 -1
  153. package/src/engine/physics/cloth/solver/constraint/cloth_leash_row.d.ts.map +1 -1
  154. package/src/engine/physics/cloth/solver/constraint/cloth_leash_row.js +353 -346
  155. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +7 -5
  156. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -1
  157. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +7 -5
  158. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +9 -9
  159. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -1
  160. package/src/engine/physics/cloth/wind/cloth_sample_air.js +26 -26
  161. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  162. package/src/engine/simulation/Ticker.d.ts +13 -0
  163. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  164. package/src/engine/simulation/Ticker.js +43 -8
  165. package/src/shade/RENDERER_CONTRACT.md +8 -2
  166. package/src/shade/playground/mesh_row_ceiling/main.js +2 -1
  167. package/src/shade/playground/skinned_blas_refit/main.d.ts.map +1 -1
  168. package/src/shade/playground/skinned_blas_refit/main.js +11 -19
  169. package/src/shade/playground/skinned_blas_refit/place_character.d.ts +6 -3
  170. package/src/shade/playground/skinned_blas_refit/place_character.d.ts.map +1 -1
  171. package/src/shade/playground/skinned_blas_refit/place_character.js +90 -90
  172. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -1
  173. package/src/shade/playground/skinned_mesh_soup/main.js +502 -509
  174. package/src/shade/playground/texture_encode/README.md +129 -109
  175. package/src/shade/playground/texture_encode/main.js +1430 -1430
  176. package/src/shade/playground/vgeo_runtime/README.md +5 -4
  177. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  178. package/src/shade/renderer/Renderer.js +6 -5
  179. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +45 -14
  180. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  181. package/src/shade/renderer/animation/GPUAnimationManager.js +316 -64
  182. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts +5 -0
  183. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  184. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +11 -1
  185. package/src/shade/renderer/atmosphere/GPUSky.d.ts +12 -0
  186. package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
  187. package/src/shade/renderer/atmosphere/GPUSky.js +162 -147
  188. package/src/shade/renderer/geometry/BoxGeometry.d.ts +1 -11
  189. package/src/shade/renderer/geometry/BoxGeometry.d.ts.map +1 -1
  190. package/src/shade/renderer/geometry/BoxGeometry.js +6 -13
  191. package/src/shade/renderer/geometry/CapsuleGeometry.d.ts +19 -0
  192. package/src/shade/renderer/geometry/CapsuleGeometry.d.ts.map +1 -0
  193. package/src/shade/renderer/geometry/CapsuleGeometry.js +24 -0
  194. package/src/shade/renderer/geometry/ConeGeometry.d.ts +20 -0
  195. package/src/shade/renderer/geometry/ConeGeometry.d.ts.map +1 -0
  196. package/src/shade/renderer/geometry/ConeGeometry.js +33 -0
  197. package/src/shade/renderer/geometry/CylinderGeometry.d.ts +21 -0
  198. package/src/shade/renderer/geometry/CylinderGeometry.d.ts.map +1 -0
  199. package/src/shade/renderer/geometry/CylinderGeometry.js +35 -0
  200. package/src/shade/renderer/geometry/PlaneGeometry.d.ts +17 -0
  201. package/src/shade/renderer/geometry/PlaneGeometry.d.ts.map +1 -0
  202. package/src/shade/renderer/geometry/PlaneGeometry.js +22 -0
  203. package/src/shade/renderer/geometry/TorusGeometry.d.ts +18 -0
  204. package/src/shade/renderer/geometry/TorusGeometry.d.ts.map +1 -0
  205. package/src/shade/renderer/geometry/TorusGeometry.js +23 -0
  206. package/src/shade/renderer/geometry/UVSphereGeometry.d.ts +21 -0
  207. package/src/shade/renderer/geometry/UVSphereGeometry.d.ts.map +1 -0
  208. package/src/shade/renderer/geometry/UVSphereGeometry.js +34 -0
  209. package/src/shade/renderer/geometry/primitives/make_capsule_geometry.d.ts +19 -0
  210. package/src/shade/renderer/geometry/primitives/make_capsule_geometry.d.ts.map +1 -0
  211. package/src/shade/renderer/geometry/primitives/make_capsule_geometry.js +205 -0
  212. package/src/shade/renderer/geometry/primitives/make_cone_geometry.d.ts +21 -0
  213. package/src/shade/renderer/geometry/primitives/make_cone_geometry.d.ts.map +1 -0
  214. package/src/shade/renderer/geometry/primitives/make_cone_geometry.js +36 -0
  215. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.d.ts +1 -1
  216. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.d.ts.map +1 -1
  217. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +44 -9
  218. package/src/shade/renderer/geometry/primitives/make_uv_sphere_geometry.d.ts +20 -0
  219. package/src/shade/renderer/geometry/primitives/make_uv_sphere_geometry.d.ts.map +1 -0
  220. package/src/shade/renderer/geometry/primitives/make_uv_sphere_geometry.js +190 -0
  221. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
  222. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +7 -2
  223. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.d.ts +26 -8
  224. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.d.ts.map +1 -1
  225. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.js +56 -39
  226. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.d.ts +7 -0
  227. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.d.ts.map +1 -1
  228. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.js +46 -4
  229. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/test_prefix_sum.d.ts.map +1 -1
  230. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/test_prefix_sum.js +4 -2
  231. package/src/shade/renderer/gpu_primitive/sort/defs.d.ts +2 -2
  232. package/src/shade/renderer/gpu_primitive/sort/defs.js +79 -79
  233. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts +1 -1
  234. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts.map +1 -1
  235. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.js +7 -3
  236. package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.d.ts.map +1 -1
  237. package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.js +10 -3
  238. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  239. package/src/shade/renderer/particles/DESIGN.md +13 -9
  240. package/src/shade/renderer/particles/GPUParticleSystem.js +1 -1
  241. package/src/shade/renderer/particles/coherence/graph_particle_bucket.d.ts +4 -3
  242. package/src/shade/renderer/particles/coherence/graph_particle_bucket.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/coherence/graph_particle_bucket.js +200 -194
  244. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  245. package/src/shade/renderer/particles/graph_particles.js +6 -2
  246. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +1 -1
  247. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +1 -1
  248. package/src/shade/renderer/particles/warmup/graph_particle_warmup.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/warmup/graph_particle_warmup.js +334 -329
  250. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  251. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  252. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +7 -2
  253. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
  254. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +16 -4
  255. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +3 -2
  256. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  257. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +3 -2
  258. package/src/shade/renderer/scene/GPUSceneContext.d.ts +9 -1
  259. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  260. package/src/shade/renderer/scene/GPUSceneContext.js +23 -1
  261. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +2444 -2345
  262. package/src/shade/renderer/texture/encode/chunk_astc_encode_block_4x4_rgb.d.ts.map +1 -1
  263. package/src/shade/renderer/texture/encode/chunk_astc_encode_block_4x4_rgb.js +312 -310
  264. package/src/shade/renderer/texture/encode/chunk_bc1_encode_block.d.ts +3 -3
  265. package/src/shade/renderer/texture/encode/chunk_bc1_encode_block.d.ts.map +1 -1
  266. package/src/shade/renderer/texture/encode/chunk_bc1_encode_block.js +383 -375
  267. package/src/shade/renderer/texture/encode/chunk_bc6h_encode_block_mode11.d.ts.map +1 -1
  268. package/src/shade/renderer/texture/encode/chunk_bc6h_encode_block_mode11.js +349 -346
  269. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block.d.ts +58 -0
  270. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block.d.ts.map +1 -0
  271. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block.js +536 -0
  272. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block_mode6.d.ts +21 -7
  273. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block_mode6.d.ts.map +1 -1
  274. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block_mode6.js +291 -327
  275. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode1.d.ts +25 -0
  276. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode1.d.ts.map +1 -0
  277. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode1.js +99 -0
  278. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode6.d.ts +5 -3
  279. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode6.d.ts.map +1 -1
  280. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode6.js +47 -71
  281. package/src/shade/renderer/texture/encode/chunk_bc7_partition_2.d.ts +10 -0
  282. package/src/shade/renderer/texture/encode/chunk_bc7_partition_2.d.ts.map +1 -0
  283. package/src/shade/renderer/texture/encode/chunk_bc7_partition_2.js +50 -0
  284. package/src/shade/renderer/texture/encode/shader_texture_encode_bc7.d.ts +4 -5
  285. package/src/shade/renderer/texture/encode/shader_texture_encode_bc7.d.ts.map +1 -1
  286. package/src/shade/renderer/texture/encode/shader_texture_encode_bc7.js +7 -8
  287. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +4 -1
  288. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  289. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +74 -3
  290. package/src/shade/renderer/volumetrics/NOTES.md +1023 -957
  291. package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts +5 -4
  292. package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts.map +1 -1
  293. package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.js +5 -4
  294. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +1 -1
  295. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +1 -1
  296. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +1 -1
  297. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +1 -1
  298. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +41 -0
  299. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +1 -0
  300. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +112 -0
  301. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +9 -23
  302. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  303. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +18 -126
  304. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +22 -0
  305. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +1 -0
  306. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.js +228 -0
  307. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  308. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +0 -53
  309. package/src/shade/renderer/water/chunk_water_transport.d.ts.map +1 -1
  310. package/src/shade/renderer/water/chunk_water_transport.js +7 -5
  311. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +1 -1
  312. package/src/shade/renderer/water/medium/GPUWaterBodies.js +8 -2
  313. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.d.ts +3 -2
  314. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.d.ts.map +1 -1
  315. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.js +3 -2
  316. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  317. package/src/shade/renderer/water/shader_water.js +3 -1
  318. package/src/shade/renderer/water/surface/GPUWaterSurface.d.ts.map +1 -1
  319. package/src/shade/renderer/water/surface/GPUWaterSurface.js +3 -1
  320. package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts +4 -3
  321. package/src/shade/renderer/water/surface/chunk_water_surface_sample.d.ts.map +1 -1
  322. package/src/shade/renderer/water/surface/chunk_water_surface_sample.js +17 -7
  323. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts.map +1 -1
  324. package/src/shade/renderer/water/surface/graph_water_tessellation.js +11 -4
  325. package/src/shade/renderer/water/surface/particles/GPUWaterParticles.d.ts.map +1 -1
  326. package/src/shade/renderer/water/surface/particles/GPUWaterParticles.js +93 -30
  327. package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.d.ts.map +1 -1
  328. package/src/shade/renderer/water/surface/particles/WATER_PARTICLE_STRUCT.js +13 -5
  329. package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts +29 -8
  330. package/src/shade/renderer/water/surface/particles/chunk_water_particle.d.ts.map +1 -1
  331. package/src/shade/renderer/water/surface/particles/chunk_water_particle.js +155 -30
  332. package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts +6 -1
  333. package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.d.ts.map +1 -1
  334. package/src/shade/renderer/water/surface/particles/chunk_water_particle_step.js +157 -39
  335. package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts +91 -21
  336. package/src/shade/renderer/water/surface/particles/shader_water_particles.d.ts.map +1 -1
  337. package/src/shade/renderer/water/surface/particles/shader_water_particles.js +442 -70
  338. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +11 -3
  339. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  340. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +3 -0
  341. package/src/shade/wgsl/validate_wgsl_source.d.ts.map +1 -1
  342. package/src/shade/wgsl/validate_wgsl_source.js +74 -3
  343. package/src/engine/graphics3/register_instance_animation.d.ts +0 -114
  344. package/src/engine/graphics3/register_instance_animation.d.ts.map +0 -1
  345. package/src/engine/graphics3/register_instance_animation.js +0 -201
@@ -1,9 +1,13 @@
1
1
  import { assert } from "../../../../core/assert.js";
2
- import { quat3_multiply_array } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
2
+ import { quat3_multiply, quat3_multiply_array } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
3
3
  import { quat3_nlerp_array } from "../../../../core/geom/3d/quaternion/quat3_nlerp.js";
4
4
  import { v3_quaternion_apply_array } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
5
5
  import { v3_quaternion_apply_inverse_array } from "../../../../core/geom/vec3/v3_quaternion_apply_inverse.js";
6
6
  import { EntityObserver } from "../../../ecs/EntityObserver.js";
7
+ import { EventType } from "../../../ecs/EventType.js";
8
+ import { GPUStateAuthorityFlags } from "../../../ecs/gpu/GPUStateAuthorityFlags.js";
9
+ import { gpu_authority_has } from "../../../ecs/gpu/gpu_authority_has.js";
10
+ import { ParentEntity } from "../../../ecs/parent/ParentEntity.js";
7
11
  import {
8
12
  TransformAttachment
9
13
  } from "../../../ecs/transform-attachment/TransformAttachment.js";
@@ -12,13 +16,14 @@ import {
12
16
  } from "../../../ecs/transform-attachment/TransformAttachmentSystem.js";
13
17
  import {
14
18
  TRANSFORM64_ROTATION_OFFSET,
15
- TRANSFORM64_TRANSLATION_OFFSET
19
+ TRANSFORM64_TRANSLATION_OFFSET,
20
+ Transform64
16
21
  } from "../../../ecs/transform/Transform64.js";
17
22
  import { t64_evaluate_world } from "../../../ecs/transform/t64_evaluate_world.js";
18
23
  import {
19
24
  collect_entity_playbacks
20
25
  } from "../../../graphics3/pose/collect_entity_playbacks.js";
21
- import { MeshSystem } from "../../../graphics3/MeshSystem.js";
26
+ import { MeshSkin } from "../../../graphics/ecs/mesh-v2/MeshSkin.js";
22
27
  import { CLOTH_SELF_RADIUS_MAX_RATIO, CLOTH_SELF_RADIUS_MIN_RATIO } from "../solver/cloth_solver_constants.js";
23
28
  import { cloth_air_changed, cloth_sample_air } from "../wind/cloth_sample_air.js";
24
29
  import { ClothStepParam } from "../solver/cloth_step_instance.js";
@@ -26,11 +31,14 @@ import { ClothStepResult } from "../solver/ClothState.js";
26
31
  import { ClothColliderSystem } from "./ClothColliderSystem.js";
27
32
  import { ClothDynamicsFlags } from "./ClothDynamicsFlags.js";
28
33
  import { ClothFlags } from "./ClothFlags.js";
34
+ import { ClothHold } from "./ClothHold.js";
29
35
  import { ClothInstance } from "./ClothInstance.js";
36
+ import { ClothPin } from "./ClothPin.js";
37
+ import { cloth_apply_holds, cloth_capture_hold, cloth_hold_step } from "./cloth_holds.js";
30
38
  import { ClothRig } from "./ClothRig.js";
31
39
  import {
32
- CLOTH_ANCHOR_ROTATION_LIMIT,
33
- CLOTH_ANCHOR_TRAVEL_REACH,
40
+ CLOTH_FRAME_ROTATION_LIMIT,
41
+ CLOTH_FRAME_TRAVEL_REACH,
34
42
  CLOTH_SLEEP_DISPLACEMENT_RATIO,
35
43
  CLOTH_SLEEP_SPEED_RATIO,
36
44
  CLOTH_SLEEP_STEPS,
@@ -61,12 +69,9 @@ import { cloth_write_back_rig } from "./cloth_write_back_rig.js";
61
69
  * # Every cloth in a scene, and everything a step needs that is not arithmetic
62
70
  *
63
71
  * One {@link ClothInstance} per {@link Cloth} component, stepped at the fixed
64
- * rate. Per step, per cloth: move the local frame with the anchor, point the
65
- * leash rows at where they measure from, gather the colliders that could touch
66
- * it, step, and put the result back on the subtree. The animated pose the
67
- * leashes are measured against is not recomposed per step — in the entity path
68
- * it is constant in the anchor's own frame, and `cloth_seed_subtree.js` bakes
69
- * it.
72
+ * rate. Per step, per cloth: take where its holders are, move the local frame,
73
+ * point the leash rows at what they measure from, gather the colliders that
74
+ * could touch it, step, and put the result back on the subtree.
70
75
  *
71
76
  * ## Why this is not a `System`
72
77
  *
@@ -101,11 +106,11 @@ import { cloth_write_back_rig } from "./cloth_write_back_rig.js";
101
106
  *
102
107
  * ## The local frame and the inertia model — §7 P7
103
108
  *
104
- * Particles live in the anchor's own frame. When the anchor moves, the cloth is
109
+ * Particles live in the cloth's frame. When the frame moves, the cloth is
105
110
  * asked how much of that motion it would like to feel:
106
111
  * `ClothDynamics#inertia = 1` keeps every particle's **world** position and
107
112
  * velocity, so a cape trails a sprinting character; `0` keeps their **local**
108
- * ones, so the cloth rides the anchor as though it were standing still. The
113
+ * ones, so the cloth rides the frame as though it were standing still. The
109
114
  * blend between the two is a lerp in the local frame, which is all the frame
110
115
  * change is.
111
116
  *
@@ -127,14 +132,32 @@ import { cloth_write_back_rig } from "./cloth_write_back_rig.js";
127
132
  * step's drag response is zero, `cloth_step` takes the uniform-gravity path,
128
133
  * and a scene produces the bytes M5 measured.
129
134
  *
135
+ * ## Holds
136
+ *
137
+ * A {@link ClothPin} on any entity holds the particles it names. This world
138
+ * keeps one {@link ClothHold} per pin, by the cloth entity it names, for as long
139
+ * as the pin exists — **outside** the seeded instance, because the offsets a
140
+ * hold takes when it begins are the hold's and must survive a re-seed. A pin is
141
+ * only ever *noticed* by the observer; everything it changes in a cloth's state
142
+ * is written in {@link advance}, which is the one window in which the main
143
+ * thread owns that state under both systems.
144
+ *
145
+ * Each step, before anything else, every holder's pose is taken — read off its
146
+ * `Transform64` when the CPU owns it, evaluated exactly when a clip does — and
147
+ * expressed in the cloth's frame. `cloth_holds.js` turns those poses into
148
+ * where the held particles go, the animated pose hung from them, and the leash
149
+ * centres.
150
+ *
130
151
  * ## Teleport — §7 P6
131
152
  *
132
- * Anchor motion past a threshold is not locomotion and is not treated as any
133
- * amount of it. `ClothFlags.TeleportKeep` carries the shape across, which in a
134
- * local frame is literally doing nothing; the default re-seeds at the animated
135
- * pose with zero velocity and opens a stabilisation window in which the
136
- * velocity limit ramps back from nothing, so the first steps after a reset
137
- * cannot produce a whip.
153
+ * Frame motion past a threshold is not locomotion and is not treated as any
154
+ * amount of it, and neither is a holder jumping that far in the frame — a ship
155
+ * the rope is tied to, moved across the map. `ClothFlags.TeleportKeep` carries
156
+ * the shape across: for the frame that is literally doing nothing, and for a
157
+ * holder it is that holder's own jump applied to what hangs from it. The default
158
+ * re-seeds at the animated pose with zero velocity and opens a stabilisation
159
+ * window in which the velocity limit ramps back from nothing, so the first steps
160
+ * after a reset cannot produce a whip.
138
161
  *
139
162
  * @author Alex Goldring
140
163
  * @copyright Company Named Limited (c) 2026
@@ -162,24 +185,6 @@ export class ClothWorld {
162
185
  */
163
186
  instances = [];
164
187
 
165
- /**
166
- * Where a skinned model instance comes from. Anything that answers
167
- * `instance_of(entity)` with a `PrefabInstance` — skins whose joints
168
- * are entities — will do, and {@link MeshSystem} is what does in a
169
- * running engine.
170
- *
171
- * Public and assignable because a garment's joints are a *rendering*
172
- * structure and `MeshSystem` needs a graphics engine, a scene and a loader
173
- * to exist — so a spec that wants joints without a renderer sets this
174
- * instead, and gets exactly the interface the system uses.
175
- *
176
- * `null` means no rig can seed, which is the same additive shape the
177
- * collider index has: a scene without one gets M1's cloth rather than an
178
- * error.
179
- * @type {{instance_of: function(number): (PrefabInstance|null)}|null}
180
- */
181
- models = null;
182
-
183
188
  /**
184
189
  * @type {EntityManager|null}
185
190
  */
@@ -292,6 +297,44 @@ export class ClothWorld {
292
297
  this
293
298
  );
294
299
 
300
+ /**
301
+ * Every {@link ClothPin} in the dataset, as the hold it makes. A pin is
302
+ * noticed here and nowhere else; what it does to a cloth is written in
303
+ * {@link advance}.
304
+ * @type {EntityObserver}
305
+ * @private
306
+ */
307
+ __pin_observer = new EntityObserver(
308
+ [ClothPin],
309
+ (pin, entity) => this.#pin_added(pin, entity),
310
+ (pin, entity) => this.#pin_removed(pin, entity),
311
+ this
312
+ );
313
+
314
+ /**
315
+ * Holds by the cloth entity they name, each list ascending by holder — so a
316
+ * particle two pins name goes to the lower holder, every time.
317
+ * @type {Map<number, ClothHold[]>}
318
+ * @private
319
+ */
320
+ #holds = new Map();
321
+
322
+ /**
323
+ * Bumped for a cloth entity whenever a hold on it appears or goes;
324
+ * {@link ClothInstance#holds_version} is what an instance last applied.
325
+ * @type {Map<number, number>}
326
+ * @private
327
+ */
328
+ #hold_versions = new Map();
329
+
330
+ /**
331
+ * The dataset the observers are attached to, for the one thing a pin needs
332
+ * done outside a step: going when its cloth's entity does.
333
+ * @type {EntityComponentDataset|null}
334
+ * @private
335
+ */
336
+ #dataset = null;
337
+
295
338
  /**
296
339
  * Resolve the systems a cloth world reads. Called from the owning system's
297
340
  * `startup`.
@@ -311,10 +354,6 @@ export class ClothWorld {
311
354
  // Optional by design. A scene with no ClothColliderSystem gets cloth
312
355
  // that does not collide, rather than cloth that throws.
313
356
  this.colliders = entityManager.getSystem(ClothColliderSystem) ?? null;
314
-
315
- if (this.models === null) {
316
- this.models = entityManager.getSystem(MeshSystem) ?? null;
317
- }
318
357
  }
319
358
 
320
359
  /**
@@ -347,7 +386,18 @@ export class ClothWorld {
347
386
  * @returns {void}
348
387
  */
349
388
  attach_dataset(dataset) {
389
+ this.#dataset = dataset;
390
+
350
391
  dataset.addObserver(this.__rig_observer, true);
392
+
393
+ // A cloth simulates fine with nothing holding it, so a scene need not
394
+ // have registered the component to have cloth in it.
395
+ dataset.registerComponentType(ClothPin);
396
+
397
+ // nor a skin, to have cloth that is not a rig
398
+ dataset.registerComponentType(MeshSkin);
399
+
400
+ dataset.addObserver(this.__pin_observer, true);
351
401
  }
352
402
 
353
403
  /**
@@ -356,6 +406,406 @@ export class ClothWorld {
356
406
  */
357
407
  detach_dataset(dataset) {
358
408
  dataset.removeObserver(this.__rig_observer, false);
409
+ dataset.removeObserver(this.__pin_observer, false);
410
+
411
+ this.#dataset = null;
412
+ }
413
+
414
+ /**
415
+ * @param {ClothPin} pin
416
+ * @param {number} holder
417
+ * @private
418
+ */
419
+ #pin_added(pin, holder) {
420
+ const cloth = pin.cloth;
421
+
422
+ let list = this.#holds.get(cloth);
423
+
424
+ if (list === undefined) {
425
+ list = [];
426
+
427
+ this.#holds.set(cloth, list);
428
+
429
+ // A pin names its cloth's entity, and an entity id is reused: when
430
+ // that entity goes, the pins naming it go with it, rather than
431
+ // waiting to hold whatever is spawned under the same id next. The
432
+ // same contract `IKGoal` has with its target.
433
+ const dataset = this.#dataset;
434
+
435
+ if (dataset !== null && dataset.entityExists(cloth)) {
436
+ dataset.addEntityEventListener(cloth, EventType.EntityRemoved, this.#cloth_entity_removed, this);
437
+ }
438
+ }
439
+
440
+ const hold = new ClothHold();
441
+
442
+ hold.holder = holder;
443
+ hold.pin = pin;
444
+
445
+ let at = list.length;
446
+
447
+ while (at > 0 && list[at - 1].holder > holder) {
448
+ at--;
449
+ }
450
+
451
+ list.splice(at, 0, hold);
452
+
453
+ this.#bump(cloth);
454
+ }
455
+
456
+ /**
457
+ * @param {ClothPin} pin
458
+ * @param {number} holder
459
+ * @private
460
+ */
461
+ #pin_removed(pin, holder) {
462
+ const cloth = pin.cloth;
463
+
464
+ const list = this.#holds.get(cloth);
465
+
466
+ if (list === undefined) {
467
+ return;
468
+ }
469
+
470
+ for (let i = 0; i < list.length; i++) {
471
+ if (list[i].holder === holder) {
472
+ list.splice(i, 1);
473
+
474
+ break;
475
+ }
476
+ }
477
+
478
+ if (list.length === 0) {
479
+ this.#holds.delete(cloth);
480
+
481
+ const dataset = this.#dataset;
482
+
483
+ if (dataset !== null && dataset.entityExists(cloth)) {
484
+ dataset.removeEntityEventListener(cloth, EventType.EntityRemoved, this.#cloth_entity_removed, this);
485
+ }
486
+ }
487
+
488
+ this.#bump(cloth);
489
+ }
490
+
491
+ /**
492
+ * A cloth's entity has gone: take every pin naming it off its holder.
493
+ *
494
+ * @param {*} event
495
+ * @param {number} cloth
496
+ * @private
497
+ */
498
+ #cloth_entity_removed(event, cloth) {
499
+ const list = this.#holds.get(cloth);
500
+
501
+ const dataset = this.#dataset;
502
+
503
+ if (list === undefined || dataset === null) {
504
+ return;
505
+ }
506
+
507
+ // Removing a pin edits the list, so walk a copy.
508
+ const holders = list.map(hold => hold.holder);
509
+
510
+ for (let i = 0; i < holders.length; i++) {
511
+ if (dataset.entityExists(holders[i])) {
512
+ dataset.removeComponentFromEntity(holders[i], ClothPin);
513
+ }
514
+ }
515
+ }
516
+
517
+ /**
518
+ * @param {number} cloth
519
+ * @private
520
+ */
521
+ #bump(cloth) {
522
+ this.#hold_versions.set(cloth, (this.#hold_versions.get(cloth) ?? 0) + 1);
523
+
524
+ const instance = this.__by_entity.get(cloth);
525
+
526
+ if (instance !== undefined) {
527
+ instance.asleep = false;
528
+ instance.still_steps = 0;
529
+ }
530
+ }
531
+
532
+ /**
533
+ * The particles a cloth has and the entities they are, enough to say
534
+ * whether a holder hangs from one of them.
535
+ *
536
+ * A holder that cloth itself moves — a link of the same rope, or anything
537
+ * hanging below one — would chase its own write-back one step behind, so it
538
+ * is refused once, by name, and holds nothing. The cloth's own entity is
539
+ * allowed: for a subtree it is the frame, which cloth reads and never writes.
540
+ *
541
+ * @param {ClothInstance} instance
542
+ * @param {ClothHold} hold
543
+ * @returns {string} empty when the hold may hold, otherwise why not
544
+ * @private
545
+ */
546
+ #refusal(instance, hold) {
547
+ const ecd = this.entityManager.dataset;
548
+
549
+ for (let entity = hold.holder, depth = 0; entity >= 0 && depth < 256; depth++) {
550
+ if (this.#is_output(instance, entity)) {
551
+ return `entity ${hold.holder} holds cloth ${instance.entity} and hangs from entity ${entity}, which that cloth moves`;
552
+ }
553
+
554
+ const attachment = ecd.getComponent(entity, TransformAttachment);
555
+
556
+ entity = attachment === undefined ? -1 : attachment.parent;
557
+ }
558
+
559
+ const particles = hold.pin.particles;
560
+
561
+ for (let k = 0; k < particles.length; k++) {
562
+ if (particles[k] >= instance.node_count) {
563
+ return `entity ${hold.holder} holds particle ${particles[k]} of cloth ${instance.entity}, which has ${instance.node_count}`;
564
+ }
565
+ }
566
+
567
+ return "";
568
+ }
569
+
570
+ /**
571
+ * @param {ClothInstance} instance
572
+ * @param {number} entity
573
+ * @returns {boolean} whether cloth writes this entity's pose
574
+ * @private
575
+ */
576
+ #is_output(instance, entity) {
577
+ const output = instance.node_output;
578
+ const entities = instance.rig === null ? instance.node_entity : instance.node_joint;
579
+
580
+ for (let node = 0; node < instance.node_count; node++) {
581
+ if (output[node] !== 0 && entities[node] === entity) {
582
+ return true;
583
+ }
584
+ }
585
+
586
+ return false;
587
+ }
588
+
589
+ /**
590
+ * Bring an instance's holds up to date with the pins that name it: pick up a
591
+ * changed set, take the offsets of any hold whose holder has just been found,
592
+ * and re-derive whatever holding decides. Nothing here runs unless something
593
+ * changed.
594
+ *
595
+ * @param {ClothInstance} instance
596
+ * @param {Float64Array} frame_translation the frame the particle positions are in
597
+ * @param {Float64Array} frame_rotation
598
+ * @returns {boolean} whether anything about what holds the cloth changed
599
+ * @private
600
+ */
601
+ #refresh_holds(instance, frame_translation, frame_rotation) {
602
+ let changed = false;
603
+
604
+ const holds = instance.holds;
605
+
606
+ for (let h = 0; h < holds.length; h++) {
607
+ const hold = holds[h];
608
+
609
+ if (!hold.captured && hold.valid && !hold.refused) {
610
+ cloth_capture_hold(instance, hold, frame_translation, frame_rotation);
611
+
612
+ changed = true;
613
+ }
614
+ }
615
+
616
+ if (changed || instance.holds_applied !== instance.holds_version) {
617
+ cloth_apply_holds(instance);
618
+
619
+ instance.holds_applied = instance.holds_version;
620
+
621
+ // A different set of holds is a different cloth to rest; the window
622
+ // that was open measured the old one.
623
+ instance.still_steps = 0;
624
+ instance.sleep_reference = null;
625
+
626
+ changed = true;
627
+ }
628
+
629
+ return changed;
630
+ }
631
+
632
+ /**
633
+ * Pick up a changed set of pins: the instance's holds become the registry's,
634
+ * each one admitted or refused.
635
+ *
636
+ * @param {ClothInstance} instance
637
+ * @private
638
+ */
639
+ #sync_holds(instance) {
640
+ const version = this.#hold_versions.get(instance.entity) ?? 0;
641
+
642
+ if (instance.holds_version === version) {
643
+ return;
644
+ }
645
+
646
+ const list = this.#holds.get(instance.entity);
647
+
648
+ instance.holds = list === undefined ? [] : list.slice();
649
+ instance.holds_version = version;
650
+
651
+ for (let h = 0; h < instance.holds.length; h++) {
652
+ const hold = instance.holds[h];
653
+
654
+ const refusal = this.#refusal(instance, hold);
655
+
656
+ hold.refused = refusal !== "";
657
+
658
+ if (hold.refused && !hold.reported) {
659
+ hold.reported = true;
660
+
661
+ console.warn(`ClothSystem: ${refusal}`);
662
+ }
663
+ }
664
+ }
665
+
666
+ /**
667
+ * Where every holder is this step: in the world, and in the cloth's frame.
668
+ *
669
+ * @param {ClothInstance} instance
670
+ * @param {Float64Array} frame_translation this step's frame
671
+ * @param {Float64Array} frame_rotation
672
+ * @returns {boolean} whether any holder moved, appeared or vanished since
673
+ * the last step
674
+ * @private
675
+ */
676
+ #evaluate_holds(instance, frame_translation, frame_rotation) {
677
+ let moved = false;
678
+
679
+ const holds = instance.holds;
680
+
681
+ for (let h = 0; h < holds.length; h++) {
682
+ const hold = holds[h];
683
+
684
+ const was_valid = hold.valid;
685
+
686
+ hold.valid = !hold.refused && this.#holder_world(hold.holder, hold.world);
687
+
688
+ if (!hold.valid) {
689
+ moved = moved || was_valid;
690
+
691
+ continue;
692
+ }
693
+
694
+ const world = hold.world;
695
+ const pose = hold.pose;
696
+
697
+ // in the frame: q_f⁻¹ · (t − t_f), q_f⁻¹ ⊗ q
698
+ HOLD_SCRATCH[0] = world[0] - frame_translation[0];
699
+ HOLD_SCRATCH[1] = world[1] - frame_translation[1];
700
+ HOLD_SCRATCH[2] = world[2] - frame_translation[2];
701
+
702
+ v3_quaternion_apply_inverse_array(pose, 0, HOLD_SCRATCH, 0, frame_rotation, 0);
703
+
704
+ FRAME_CONJUGATE[0] = -frame_rotation[0];
705
+ FRAME_CONJUGATE[1] = -frame_rotation[1];
706
+ FRAME_CONJUGATE[2] = -frame_rotation[2];
707
+ FRAME_CONJUGATE[3] = frame_rotation[3];
708
+
709
+ quat3_multiply_array(pose, 3, FRAME_CONJUGATE, 0, world, 3);
710
+
711
+ if (!hold.has_previous) {
712
+ moved = true;
713
+
714
+ continue;
715
+ }
716
+
717
+ const before = hold.world_previous;
718
+
719
+ for (let i = 0; i < 7; i++) {
720
+ if (world[i] !== before[i]) {
721
+ moved = true;
722
+
723
+ break;
724
+ }
725
+ }
726
+ }
727
+
728
+ return moved;
729
+ }
730
+
731
+ /**
732
+ * A holder's world pose, `[tx, ty, tz, qx, qy, qz, qw]`.
733
+ *
734
+ * Read off its `Transform64` when the CPU owns that, which is every prop and
735
+ * every hand a script moves. A transform the GPU composes — a bone a clip
736
+ * drives — holds the rest pose under wherever the CPU last composed it, so
737
+ * it is evaluated exactly instead, under the clips of every model on its
738
+ * chain: a pole in a hand belongs to one model and hangs from another's
739
+ * bones, and evaluating it under only its own would put it where the hand
740
+ * was in the bind pose.
741
+ *
742
+ * @param {number} entity
743
+ * @param {Float64Array} out
744
+ * @returns {boolean} false when the entity has no pose to give yet — gone,
745
+ * or hanging from a parent that has no transform
746
+ * @private
747
+ */
748
+ #holder_world(entity, out) {
749
+ const ecd = this.entityManager.dataset;
750
+
751
+ if (!ecd.entityExists(entity)) {
752
+ return false;
753
+ }
754
+
755
+ const transform = ecd.getComponent(entity, Transform64);
756
+
757
+ if (transform === undefined) {
758
+ return false;
759
+ }
760
+
761
+ let source = transform;
762
+
763
+ if (gpu_authority_has(ecd, entity, GPUStateAuthorityFlags.Transform64)) {
764
+ const playbacks = PLAYBACKS;
765
+
766
+ playbacks.length = 0;
767
+
768
+ // every model on the chain, each once; and a chain that is not
769
+ // complete yet is not one to evaluate
770
+ MODELS.length = 0;
771
+
772
+ for (let e = entity, depth = 0; e >= 0 && depth < 256; depth++) {
773
+ if (!ecd.entityExists(e) || ecd.getComponent(e, Transform64) === undefined) {
774
+ playbacks.length = 0;
775
+
776
+ return false;
777
+ }
778
+
779
+ const owner = ecd.getComponent(e, ParentEntity);
780
+ const model = owner === undefined || owner === null ? e : owner.entity;
781
+
782
+ if (MODELS.indexOf(model) === -1) {
783
+ MODELS.push(model);
784
+
785
+ collect_entity_playbacks(playbacks, this.entityManager, model);
786
+ }
787
+
788
+ const attachment = ecd.getComponent(e, TransformAttachment);
789
+
790
+ e = attachment === undefined ? -1 : attachment.parent;
791
+ }
792
+
793
+ t64_evaluate_world(EVALUATED, ecd, entity, playbacks);
794
+
795
+ playbacks.length = 0;
796
+
797
+ source = EVALUATED;
798
+ }
799
+
800
+ out[0] = source[TRANSFORM64_TRANSLATION_OFFSET];
801
+ out[1] = source[TRANSFORM64_TRANSLATION_OFFSET + 1];
802
+ out[2] = source[TRANSFORM64_TRANSLATION_OFFSET + 2];
803
+ out[3] = source[TRANSFORM64_ROTATION_OFFSET];
804
+ out[4] = source[TRANSFORM64_ROTATION_OFFSET + 1];
805
+ out[5] = source[TRANSFORM64_ROTATION_OFFSET + 2];
806
+ out[6] = source[TRANSFORM64_ROTATION_OFFSET + 3];
807
+
808
+ return true;
359
809
  }
360
810
 
361
811
  /**
@@ -489,13 +939,14 @@ export class ClothWorld {
489
939
  }
490
940
 
491
941
  instance.seeded = false;
492
- instance.anchor_valid = false;
942
+ instance.frame_valid = false;
493
943
 
494
944
  return instance;
495
945
  }
496
946
 
497
947
  /**
498
- * Wake a sleeping cloth. Anchor motion and a `ClothDynamics` change do this
948
+ * Wake a sleeping cloth. Frame motion, a holder moving or a hold changing,
949
+ * and a `ClothDynamics` change do this
499
950
  * on their own; a caller that has changed the world in some way the cloth
500
951
  * cannot see — M6's wind, for one — says so here.
501
952
  *
@@ -519,6 +970,37 @@ export class ClothWorld {
519
970
  return this.__by_entity.get(entity);
520
971
  }
521
972
 
973
+ /**
974
+ * Which particle of a cloth an entity is: a link of a subtree cloth, or a
975
+ * joint of a rigged one — the number a {@link ClothPin} names it by.
976
+ *
977
+ * For code that has the entity rather than the number, which is what a hand
978
+ * closing on a rope has. It answers once the cloth has seeded, which is its
979
+ * first step.
980
+ *
981
+ * @param {number} cloth the entity carrying the `Cloth`
982
+ * @param {number} entity
983
+ * @returns {number} the particle, or `−1` when the entity is not one of the
984
+ * cloth's, or the cloth has not seeded yet
985
+ */
986
+ particle_of(cloth, entity) {
987
+ const instance = this.__by_entity.get(cloth);
988
+
989
+ if (instance === undefined || !instance.seeded) {
990
+ return -1;
991
+ }
992
+
993
+ const entities = instance.rig === null ? instance.node_entity : instance.node_joint;
994
+
995
+ for (let node = 0; node < instance.node_count; node++) {
996
+ if (entities[node] === entity) {
997
+ return node;
998
+ }
999
+ }
1000
+
1001
+ return -1;
1002
+ }
1003
+
522
1004
  /**
523
1005
  * Everything that happens once per tick rather than once per cloth.
524
1006
  *
@@ -640,9 +1122,9 @@ export class ClothWorld {
640
1122
  * simulates the joints of the skinned model the entity is showing. There is
641
1123
  * no mode field anywhere, and the solver does not know which ran.
642
1124
  *
643
- * A rig can fail to seed for reasons that are not errors — the model has
644
- * not loaded, or nothing in the manager hands out model instances — and one
645
- * that does is left unseeded for the next step to try again. A rig that
1125
+ * A rig can fail to seed for reasons that are not errors — it names no skin
1126
+ * yet, or the entity it names carries none yet, a model still loading — and
1127
+ * one that does is left unseeded for the next step to try again. A rig that
646
1128
  * fails for a reason that *is* an error says so once and then stops, because
647
1129
  * a proxy that does not fit its skin will not start fitting it.
648
1130
  *
@@ -659,6 +1141,10 @@ export class ClothWorld {
659
1141
 
660
1142
  cloth_seed_subtree(instance, ecd, this.attachments, dt);
661
1143
 
1144
+ // A new state holds nothing yet: the holds — offsets and all — are
1145
+ // applied to it on its first step.
1146
+ instance.holds_applied = -1;
1147
+
662
1148
  return;
663
1149
  }
664
1150
 
@@ -673,21 +1159,16 @@ export class ClothWorld {
673
1159
  return;
674
1160
  }
675
1161
 
676
- if (this.models === null) {
677
- this.#refuse(instance, `entity ${instance.entity} has a ClothRig and nothing hands out model instances`);
1162
+ const skin = rig.skin === -1 || !ecd.entityExists(rig.skin)
1163
+ ? undefined
1164
+ : ecd.getComponent(rig.skin, MeshSkin);
678
1165
 
1166
+ if (skin === undefined) {
1167
+ // Not named yet, or not there yet: still loading. Not an error, and
1168
+ // not worth a message.
679
1169
  return;
680
1170
  }
681
1171
 
682
- const model = this.models.instance_of(instance.entity);
683
-
684
- if (model === null || model === undefined) {
685
- // Still loading. Not an error, and not worth a message.
686
- return;
687
- }
688
-
689
- const skin = model.skins[rig.skin];
690
-
691
1172
  const error = rig.proxy.checkAgainst(skin);
692
1173
 
693
1174
  if (error !== "") {
@@ -699,6 +1180,8 @@ export class ClothWorld {
699
1180
  instance.rig = rig;
700
1181
 
701
1182
  cloth_seed_rig(instance, rig.proxy, skin.joints, ecd, dt);
1183
+
1184
+ instance.holds_applied = -1;
702
1185
  }
703
1186
 
704
1187
  /**
@@ -720,8 +1203,9 @@ export class ClothWorld {
720
1203
 
721
1204
  /**
722
1205
  * Carry one seeded cloth to the edge of its step and write the parameters
723
- * the step will run on: whatever the anchor did to the local frame, where
724
- * the leash rows measure from, and the colliders within reach.
1206
+ * the step will run on: whatever the frame and the holders did, where the
1207
+ * held particles go, where the leash rows measure from, and the colliders
1208
+ * within reach.
725
1209
  *
726
1210
  * @param {ClothInstance} instance
727
1211
  * @param {number} dt seconds
@@ -760,7 +1244,7 @@ export class ClothWorld {
760
1244
 
761
1245
  const transform = instance.rig === null
762
1246
  ? instance.transform
763
- : this.#anchor_pose(instance);
1247
+ : this.#frame_pose(instance);
764
1248
 
765
1249
  const tx = transform[TRANSFORM64_TRANSLATION_OFFSET];
766
1250
  const ty = transform[TRANSFORM64_TRANSLATION_OFFSET + 1];
@@ -771,15 +1255,38 @@ export class ClothWorld {
771
1255
  const qz = transform[TRANSFORM64_ROTATION_OFFSET + 2];
772
1256
  const qw = transform[TRANSFORM64_ROTATION_OFFSET + 3];
773
1257
 
774
- const previous_translation = instance.anchor_translation;
775
- const previous_rotation = instance.anchor_rotation;
1258
+ const previous_translation = instance.frame_translation;
1259
+ const previous_rotation = instance.frame_rotation;
1260
+
1261
+ FRAME_TRANSLATION[0] = tx;
1262
+ FRAME_TRANSLATION[1] = ty;
1263
+ FRAME_TRANSLATION[2] = tz;
1264
+
1265
+ FRAME_ROTATION[0] = qx;
1266
+ FRAME_ROTATION[1] = qy;
1267
+ FRAME_ROTATION[2] = qz;
1268
+ FRAME_ROTATION[3] = qw;
1269
+
1270
+ // What holds the cloth, before anything that might decide it is asleep:
1271
+ // a hold appearing or going is a reason to wake, and so is a holder
1272
+ // moving.
1273
+ this.#sync_holds(instance);
1274
+
1275
+ const holders_moved = this.#evaluate_holds(instance, FRAME_TRANSLATION, FRAME_ROTATION);
1276
+
1277
+ // Positions are still in the frame they were last stepped in, which on
1278
+ // a cloth's first step is this one; a hold begins where its particles
1279
+ // are, so that is the frame its offsets are taken in.
1280
+ const holds_changed = instance.frame_valid
1281
+ ? this.#refresh_holds(instance, previous_translation, previous_rotation)
1282
+ : this.#refresh_holds(instance, FRAME_TRANSLATION, FRAME_ROTATION);
776
1283
 
777
1284
  if (instance.cloth.blend !== instance.blend_seen) {
778
1285
  instance.asleep = false;
779
1286
  instance.still_steps = 0;
780
1287
  }
781
1288
 
782
- const moved = instance.anchor_valid && (
1289
+ const moved = instance.frame_valid && (
783
1290
  previous_translation[0] !== tx
784
1291
  || previous_translation[1] !== ty
785
1292
  || previous_translation[2] !== tz
@@ -789,7 +1296,7 @@ export class ClothWorld {
789
1296
  || previous_rotation[3] !== qw
790
1297
  );
791
1298
 
792
- if (moved) {
1299
+ if (moved || holders_moved || holds_changed) {
793
1300
  instance.asleep = false;
794
1301
  instance.still_steps = 0;
795
1302
  }
@@ -814,7 +1321,7 @@ export class ClothWorld {
814
1321
  // cheaper than the step it decides whether to skip.
815
1322
  if (cloth_air_changed(
816
1323
  instance.state, this.wind,
817
- instance.anchor_translation, instance.anchor_rotation,
1324
+ instance.frame_translation, instance.frame_rotation,
818
1325
  instance.air_seen, CLOTH_SLEEP_SPEED_RATIO * instance.reach,
819
1326
  instance.age
820
1327
  )) {
@@ -831,33 +1338,68 @@ export class ClothWorld {
831
1338
  return false;
832
1339
  }
833
1340
 
834
- if (moved) {
835
- const displacement = Math.hypot(tx - previous_translation[0], ty - previous_translation[1], tz - previous_translation[2]);
1341
+ // §7 P6. Locomotion moves a frame a fraction of the cloth's own reach a
1342
+ // step; a jump past this is something else, whether it is the frame
1343
+ // that jumped or something holding the cloth, measured in the frame.
1344
+ const threshold = Math.max(CLOTH_TELEPORT_REACH * instance.reach, CLOTH_TELEPORT_FLOOR);
836
1345
 
837
- const teleport = displacement > Math.max(
838
- CLOTH_TELEPORT_REACH * instance.reach,
839
- CLOTH_TELEPORT_FLOOR
840
- );
1346
+ const frame_jumped = moved && Math.hypot(
1347
+ tx - previous_translation[0], ty - previous_translation[1], tz - previous_translation[2]
1348
+ ) > threshold;
841
1349
 
842
- if (teleport) {
843
- this.#teleport(instance);
1350
+ let holder_jumped = false;
1351
+
1352
+ const holds = instance.holds;
1353
+
1354
+ for (let h = 0; h < holds.length; h++) {
1355
+ const hold = holds[h];
1356
+
1357
+ if (!hold.valid || !hold.has_previous || !hold.captured) {
1358
+ continue;
1359
+ }
1360
+
1361
+ const pose = hold.pose;
1362
+ const before = hold.pose_previous;
1363
+
1364
+ if (Math.hypot(pose[0] - before[0], pose[1] - before[1], pose[2] - before[2]) > threshold) {
1365
+ holder_jumped = true;
1366
+ }
1367
+ }
1368
+
1369
+ const keep = (instance.cloth.flags & ClothFlags.TeleportKeep) !== 0;
1370
+
1371
+ let reset = false;
1372
+
1373
+ if (frame_jumped || holder_jumped) {
1374
+ instance.teleport_count++;
1375
+
1376
+ if (keep) {
1377
+ instance.holds_dirty = true;
1378
+
1379
+ // The frame's jump needs nothing: the particles are in its frame.
1380
+ // A holder's is carried onto what hangs from it.
1381
+ for (let h = 0; h < holds.length; h++) {
1382
+ const hold = holds[h];
1383
+
1384
+ if (hold.valid && hold.has_previous && hold.captured) {
1385
+ carry_with_holder(instance, h);
1386
+ }
1387
+ }
844
1388
  } else {
845
- // the new pose goes in as an array: handed over as seven numbers, each was boxed
846
- // wherever the call was not inlined
847
- ANCHOR_POSE[0] = tx;
848
- ANCHOR_POSE[1] = ty;
849
- ANCHOR_POSE[2] = tz;
850
- ANCHOR_POSE[3] = qx;
851
- ANCHOR_POSE[4] = qy;
852
- ANCHOR_POSE[5] = qz;
853
- ANCHOR_POSE[6] = qw;
854
-
855
- move_local_frame(instance, ANCHOR_POSE, previous_translation, previous_rotation);
1389
+ reset = true;
856
1390
  }
857
- } else if (instance.anchor_valid && instance.cloth.dynamics.inertia > 0) {
858
- // The frame is identical. Preserve the pinning side effect of
859
- // move_local_frame without rewriting every free position/velocity.
860
- pin_anchor(instance);
1391
+ } else if (moved) {
1392
+ // the new pose goes in as an array: handed over as seven numbers, each was boxed
1393
+ // wherever the call was not inlined
1394
+ FRAME_POSE[0] = tx;
1395
+ FRAME_POSE[1] = ty;
1396
+ FRAME_POSE[2] = tz;
1397
+ FRAME_POSE[3] = qx;
1398
+ FRAME_POSE[4] = qy;
1399
+ FRAME_POSE[5] = qz;
1400
+ FRAME_POSE[6] = qw;
1401
+
1402
+ move_local_frame(instance, FRAME_POSE, previous_translation, previous_rotation);
861
1403
  }
862
1404
 
863
1405
  previous_translation[0] = tx;
@@ -869,13 +1411,30 @@ export class ClothWorld {
869
1411
  previous_rotation[2] = qz;
870
1412
  previous_rotation[3] = qw;
871
1413
 
872
- instance.anchor_valid = true;
1414
+ instance.frame_valid = true;
1415
+
1416
+ // Where every held particle goes, the pose hung from them, the leash
1417
+ // centres — from this step's holder poses.
1418
+ cloth_hold_step(instance);
1419
+
1420
+ if (reset) {
1421
+ this.#reset(instance);
1422
+ }
873
1423
 
874
- refresh_leash_targets(instance);
1424
+ for (let h = 0; h < holds.length; h++) {
1425
+ const hold = holds[h];
1426
+
1427
+ if (hold.valid) {
1428
+ hold.pose_previous.set(hold.pose);
1429
+ hold.world_previous.set(hold.world);
1430
+
1431
+ hold.has_previous = true;
1432
+ }
1433
+ }
875
1434
 
876
1435
  const dynamics = instance.cloth.dynamics;
877
1436
 
878
- // Gravity is a world vector and the solve is in the anchor's frame.
1437
+ // Gravity is a world vector and the solve is in the cloth's frame.
879
1438
  const gravity = this.gravity;
880
1439
 
881
1440
  // `previous_rotation` has just been set to this step's `q`; vector and rotation go in as arrays
@@ -894,7 +1453,8 @@ export class ClothWorld {
894
1453
  // nothing beyond it — `cloth_gather_reach` carries the derivation,
895
1454
  // and why the velocity clamp that used to stand here was both far
896
1455
  // too wide and missing two terms.
897
- const reach = cloth_gather_reach(state, this.colliders.index, dt);
1456
+ // and as far again as a holder drags it, which no velocity says
1457
+ const reach = cloth_gather_reach(state, this.colliders.index, dt) + instance.kinematic_travel;
898
1458
 
899
1459
  cloth_gather_colliders(instance, this.colliders.index, reach);
900
1460
 
@@ -921,7 +1481,7 @@ export class ClothWorld {
921
1481
 
922
1482
  instance.air_varies = cloth_sample_air(
923
1483
  state, this.wind,
924
- instance.anchor_translation, instance.anchor_rotation,
1484
+ instance.frame_translation, instance.frame_rotation,
925
1485
  instance.air_seen, instance.age
926
1486
  );
927
1487
  } else {
@@ -929,6 +1489,7 @@ export class ClothWorld {
929
1489
  }
930
1490
 
931
1491
  params[offset + ClothStepParam.DRAG_RESPONSE] = drag_response;
1492
+ params[offset + ClothStepParam.KINEMATIC_TRAVEL] = instance.kinematic_travel;
932
1493
 
933
1494
  const self_collision = (dynamics.flags & ClothDynamicsFlags.SelfCollision) !== 0;
934
1495
 
@@ -980,8 +1541,10 @@ export class ClothWorld {
980
1541
  }
981
1542
 
982
1543
  /**
983
- * The anchor joint's world pose, computed on the CPU from the clips the
984
- * entity is playing.
1544
+ * A rig's frame joint's world pose, computed on the CPU from the clips the
1545
+ * entity is playing — and, while the playbacks are in hand, the pose in that
1546
+ * frame of every bone the garment hangs from, for the write-back to express
1547
+ * the garment's roots against.
985
1548
  *
986
1549
  * **Computed, never read back**, for two reasons that are both the engine's
987
1550
  * own. A joint of an animated skeleton is the GPU's (`GPUStateAuthority`),
@@ -997,36 +1560,62 @@ export class ClothWorld {
997
1560
  * @returns {Transform64}
998
1561
  * @private
999
1562
  */
1000
- #anchor_pose(instance) {
1563
+ #frame_pose(instance) {
1001
1564
  const playbacks = PLAYBACKS;
1002
1565
 
1003
1566
  playbacks.length = 0;
1004
1567
 
1005
1568
  collect_entity_playbacks(playbacks, this.entityManager, instance.entity);
1006
1569
 
1007
- t64_evaluate_world(instance.anchor_pose, this.entityManager.dataset, instance.node_joint[0], playbacks);
1570
+ const ecd = this.entityManager.dataset;
1571
+
1572
+ const frame = instance.frame_pose;
1573
+
1574
+ t64_evaluate_world(frame, ecd, instance.frame_entity, playbacks);
1575
+
1576
+ const node_output = instance.node_output;
1577
+ const boundary = instance.boundary_pose;
1578
+
1579
+ for (let node = 0; node < instance.node_count; node++) {
1580
+ if (node_output[node] !== 0) {
1581
+ continue;
1582
+ }
1583
+
1584
+ t64_evaluate_world(EVALUATED, ecd, instance.node_joint[node], playbacks);
1585
+
1586
+ const b = node * 7;
1587
+
1588
+ HOLD_SCRATCH[0] = EVALUATED[TRANSFORM64_TRANSLATION_OFFSET] - frame[TRANSFORM64_TRANSLATION_OFFSET];
1589
+ HOLD_SCRATCH[1] = EVALUATED[TRANSFORM64_TRANSLATION_OFFSET + 1] - frame[TRANSFORM64_TRANSLATION_OFFSET + 1];
1590
+ HOLD_SCRATCH[2] = EVALUATED[TRANSFORM64_TRANSLATION_OFFSET + 2] - frame[TRANSFORM64_TRANSLATION_OFFSET + 2];
1591
+
1592
+ v3_quaternion_apply_inverse_array(boundary, b, HOLD_SCRATCH, 0, frame, TRANSFORM64_ROTATION_OFFSET);
1593
+
1594
+ FRAME_CONJUGATE[0] = -frame[TRANSFORM64_ROTATION_OFFSET];
1595
+ FRAME_CONJUGATE[1] = -frame[TRANSFORM64_ROTATION_OFFSET + 1];
1596
+ FRAME_CONJUGATE[2] = -frame[TRANSFORM64_ROTATION_OFFSET + 2];
1597
+ FRAME_CONJUGATE[3] = frame[TRANSFORM64_ROTATION_OFFSET + 3];
1598
+
1599
+ quat3_multiply_array(boundary, b + 3, FRAME_CONJUGATE, 0, EVALUATED, TRANSFORM64_ROTATION_OFFSET);
1600
+ }
1008
1601
 
1009
1602
  playbacks.length = 0;
1010
1603
 
1011
- return instance.anchor_pose;
1604
+ return frame;
1012
1605
  }
1013
1606
 
1014
1607
  /**
1608
+ * §7 P6's `Reset`: every particle onto the animated pose — the rest pose
1609
+ * hung from where its holders now are — at rest, with nothing carried over.
1610
+ *
1611
+ * A held particle lands on its target, and its target is where the predict
1612
+ * carries it to, so it does not move again this step: the jump is taken
1613
+ * between steps rather than dragged through one.
1614
+ *
1015
1615
  * @param {ClothInstance} instance
1016
1616
  * @private
1017
1617
  */
1018
- #teleport(instance) {
1019
- instance.teleport_count++;
1020
-
1021
- if ((instance.cloth.flags & ClothFlags.TeleportKeep) !== 0) {
1022
- // `Keep` carries the cloth's shape across the jump. Particles are
1023
- // stored relative to the anchor, so that is exactly what happens if
1024
- // this function does nothing to them — the frame moved and they did
1025
- // not, which is MagicaCloth2's rigid delta expressed in the frame
1026
- // that makes it free.
1027
- return;
1028
- }
1029
-
1618
+ #reset(instance) {
1030
1619
  const state = instance.state;
1031
1620
 
1032
1621
  const node_count = instance.node_count;
@@ -1043,6 +1632,10 @@ export class ClothWorld {
1043
1632
  }
1044
1633
 
1045
1634
  state.position_previous.set(state.position);
1635
+ state.kinematic_target.set(state.position);
1636
+
1637
+ instance.kinematic_travel = 0;
1638
+ instance.holds_dirty = true;
1046
1639
  state.velocity.fill(0);
1047
1640
  state.inertia_blend.fill(0);
1048
1641
 
@@ -1078,11 +1671,51 @@ const LOCAL_GRAVITY = new Float64Array(3);
1078
1671
  const FRAME = new Float64Array(8);
1079
1672
 
1080
1673
  /**
1081
- * The anchor's pose this step, `[tx, ty, tz, qx, qy, qz, qw]`, as {@link move_local_frame} takes it.
1674
+ * The frame's pose this step, `[tx, ty, tz, qx, qy, qz, qw]`, as {@link move_local_frame} takes it.
1675
+ * @type {Float64Array}
1676
+ * @private
1677
+ */
1678
+ const FRAME_POSE = new Float64Array(7);
1679
+
1680
+ /**
1681
+ * The frame's translation and rotation this step, apart, as the holds take them.
1082
1682
  * @type {Float64Array}
1083
1683
  * @private
1084
1684
  */
1085
- const ANCHOR_POSE = new Float64Array(7);
1685
+ const FRAME_TRANSLATION = new Float64Array(3);
1686
+
1687
+ /**
1688
+ * @type {Float64Array}
1689
+ * @private
1690
+ */
1691
+ const FRAME_ROTATION = new Float64Array(4);
1692
+
1693
+ /**
1694
+ * @type {Float64Array}
1695
+ * @private
1696
+ */
1697
+ const FRAME_CONJUGATE = new Float64Array(4);
1698
+
1699
+ /**
1700
+ * @type {Float64Array}
1701
+ * @private
1702
+ */
1703
+ const HOLD_SCRATCH = new Float64Array(4);
1704
+
1705
+ /**
1706
+ * An exactly evaluated world pose: a holder a clip drives, a bone a garment
1707
+ * hangs from.
1708
+ * @type {Transform64}
1709
+ * @private
1710
+ */
1711
+ const EVALUATED = new Transform64();
1712
+
1713
+ /**
1714
+ * The models a holder's chain passes through, each once.
1715
+ * @type {number[]}
1716
+ * @private
1717
+ */
1718
+ const MODELS = [];
1086
1719
 
1087
1720
  /**
1088
1721
  * {@link move_local_frame}'s `q_now⁻¹`, `[-qx, -qy, -qz, qw]`.
@@ -1100,27 +1733,27 @@ const POSE_CONJUGATE = new Float64Array(4);
1100
1733
  const IDENTITY_ROTATION = new Float64Array([0, 0, 0, 1]);
1101
1734
 
1102
1735
  /**
1103
- * Carry the particles across a change of anchor pose, by however much of it the
1736
+ * Carry the free particles across a change of frame pose, by however much of it the
1104
1737
  * fabric is willing to feel. §7 P7.
1105
1738
  *
1106
1739
  * A particle at local `p` was at world `t_prev + q_prev·p`. Holding that world
1107
1740
  * position fixed, its local coordinates become `D + Q·p` with
1108
1741
  * `D = q_now⁻¹·(t_prev − t_now)` and `Q = q_now⁻¹ ⊗ q_prev`. Feeling *all* of
1109
- * the anchor's motion is taking that; feeling *none* is leaving `p` alone; and
1742
+ * the frame's motion is taking that; feeling *none* is leaving `p` alone; and
1110
1743
  * `inertia` is the lerp between them. Velocity is carried the same way, minus
1111
1744
  * the translation, since a translation of the frame does not rotate a vector.
1112
1745
  *
1113
1746
  * `D` and `Q` are limited before the lerp. The limits are the cloth's own reach
1114
- * per step and a fixed angle per step, which is what makes an anchor that
1747
+ * per step and a fixed angle per step, which is what makes a frame that
1115
1748
  * teleports a metre — or spins — something a cape *lags behind* rather than
1116
1749
  * something it is fired out of.
1117
1750
  *
1118
1751
  * Every vector and quaternion goes to its function as an array and an offset: handed over as numbers,
1119
1752
  * each was boxed into a heap object wherever the call was not inlined, which until V8 optimises this
1120
- * is every call, twice per particle per step of a moving anchor.
1753
+ * is every call, twice per particle per step of a moving frame.
1121
1754
  *
1122
1755
  * @param {ClothInstance} instance
1123
- * @param {Float64Array} pose the anchor's pose now, `[tx, ty, tz, qx, qy, qz, qw]`
1756
+ * @param {Float64Array} pose the frame's pose now, `[tx, ty, tz, qx, qy, qz, qw]`
1124
1757
  * @param {Float64Array} previous_translation
1125
1758
  * @param {Float64Array} previous_rotation
1126
1759
  * @private
@@ -1142,7 +1775,7 @@ function move_local_frame(instance, pose, previous_translation, previous_rotatio
1142
1775
 
1143
1776
  v3_quaternion_apply_inverse_array(FRAME, 0, FRAME, 0, pose, 3);
1144
1777
 
1145
- const travel_limit = CLOTH_ANCHOR_TRAVEL_REACH * instance.reach;
1778
+ const travel_limit = CLOTH_FRAME_TRAVEL_REACH * instance.reach;
1146
1779
 
1147
1780
  const travel = Math.hypot(FRAME[0], FRAME[1], FRAME[2]);
1148
1781
 
@@ -1164,10 +1797,10 @@ function move_local_frame(instance, pose, previous_translation, previous_rotatio
1164
1797
  // `|w|` is `cos(θ/2)`, so this is the angle test without an `acos`.
1165
1798
  const half_angle_cos = FRAME[7] < 0 ? -FRAME[7] : FRAME[7];
1166
1799
 
1167
- if (half_angle_cos < Math.cos(CLOTH_ANCHOR_ROTATION_LIMIT * 0.5)) {
1800
+ if (half_angle_cos < Math.cos(CLOTH_FRAME_ROTATION_LIMIT * 0.5)) {
1168
1801
  const angle = 2 * Math.acos(half_angle_cos > 1 ? 1 : half_angle_cos);
1169
1802
 
1170
- quat3_nlerp_array(FRAME, 4, IDENTITY_ROTATION, 0, FRAME, 4, CLOTH_ANCHOR_ROTATION_LIMIT / angle);
1803
+ quat3_nlerp_array(FRAME, 4, IDENTITY_ROTATION, 0, FRAME, 4, CLOTH_FRAME_ROTATION_LIMIT / angle);
1171
1804
  }
1172
1805
 
1173
1806
  const state = instance.state;
@@ -1181,7 +1814,16 @@ function move_local_frame(instance, pose, previous_translation, previous_rotatio
1181
1814
  const dy = FRAME[1];
1182
1815
  const dz = FRAME[2];
1183
1816
 
1817
+ const mass_inverse = state.mass_inverse;
1818
+
1184
1819
  for (let i = 0; i < particle_count; i++) {
1820
+ // A held particle belongs to its holder, not to the world: it goes where
1821
+ // its holder takes it, through the step, and not where the frame's
1822
+ // motion would have left it.
1823
+ if (mass_inverse[i] === 0) {
1824
+ continue;
1825
+ }
1826
+
1185
1827
  const p = i * 3;
1186
1828
 
1187
1829
  v3_quaternion_apply_array(CARRIED, 0, position, p, FRAME, 4);
@@ -1196,30 +1838,68 @@ function move_local_frame(instance, pose, previous_translation, previous_rotatio
1196
1838
  velocity[p + 1] += inertia * (CARRIED[1] - velocity[p + 1]);
1197
1839
  velocity[p + 2] += inertia * (CARRIED[2] - velocity[p + 2]);
1198
1840
  }
1199
-
1200
- pin_anchor(instance);
1201
1841
  }
1202
1842
 
1203
1843
  /**
1844
+ * `ClothFlags.TeleportKeep` for a holder that jumped: its own jump, in the
1845
+ * frame, applied to everything hanging from it — the particles it holds and
1846
+ * every free one whose nearest held particle is one of them — positions and
1847
+ * velocities alike. What hangs from another holder stays where it is.
1848
+ *
1204
1849
  * @param {ClothInstance} instance
1850
+ * @param {number} h index into {@link ClothInstance#holds}
1205
1851
  * @private
1206
1852
  */
1207
- function pin_anchor(instance) {
1208
- const position = instance.state.position;
1209
- const velocity = instance.state.velocity;
1210
- // The anchor is pinned and is the origin of the frame it anchors, so it is
1211
- // the one particle that must not be carried anywhere.
1212
- const anchor = instance.particle_of_node[0] * 3;
1213
-
1214
- position[anchor] = 0;
1215
- position[anchor + 1] = 0;
1216
- position[anchor + 2] = 0;
1217
-
1218
- velocity[anchor] = 0;
1219
- velocity[anchor + 1] = 0;
1220
- velocity[anchor + 2] = 0;
1853
+ function carry_with_holder(instance, h) {
1854
+ const hold = instance.holds[h];
1855
+
1856
+ const now = hold.pose;
1857
+ const before = hold.pose_previous;
1858
+
1859
+ // the jump's rotation, q_now ⊗ q_before⁻¹
1860
+ quat3_multiply(
1861
+ JUMP, 0,
1862
+ now[3], now[4], now[5], now[6],
1863
+ -before[3], -before[4], -before[5], before[6]
1864
+ );
1865
+
1866
+ const state = instance.state;
1867
+ const position = state.position;
1868
+ const velocity = state.velocity;
1869
+
1870
+ const node_hold = instance.node_hold;
1871
+ const source = instance.tether_source;
1872
+ const particle_of_node = instance.particle_of_node;
1873
+
1874
+ for (let node = 0; node < instance.node_count; node++) {
1875
+ const owner = node_hold[node] !== -1 ? node_hold[node] : (source[node] === -1 ? -1 : node_hold[source[node]]);
1876
+
1877
+ if (owner !== h) {
1878
+ continue;
1879
+ }
1880
+
1881
+ const p = particle_of_node[node] * 3;
1882
+
1883
+ CARRIED[0] = position[p] - before[0];
1884
+ CARRIED[1] = position[p + 1] - before[1];
1885
+ CARRIED[2] = position[p + 2] - before[2];
1886
+
1887
+ v3_quaternion_apply_array(position, p, CARRIED, 0, JUMP, 0);
1888
+
1889
+ position[p] += now[0];
1890
+ position[p + 1] += now[1];
1891
+ position[p + 2] += now[2];
1892
+
1893
+ v3_quaternion_apply_array(velocity, p, velocity, p, JUMP, 0);
1894
+ }
1221
1895
  }
1222
1896
 
1897
+ /**
1898
+ * @type {Float64Array}
1899
+ * @private
1900
+ */
1901
+ const JUMP = new Float64Array(4);
1902
+
1223
1903
  /**
1224
1904
  * @type {Float64Array}
1225
1905
  * @private
@@ -1285,46 +1965,3 @@ function cloth_inside_sleep_window(instance) {
1285
1965
  return true;
1286
1966
  }
1287
1967
 
1288
- /**
1289
- * Point every leash row at where it measures from: a slack row at the
1290
- * particle's animated position, a tether at the anchor.
1291
- *
1292
- * @param {ClothInstance} instance
1293
- * @private
1294
- */
1295
- function refresh_leash_targets(instance) {
1296
- const state = instance.state;
1297
-
1298
- const target = state.leash_target;
1299
-
1300
- const animated = instance.animated_position;
1301
-
1302
- const anchor = instance.particle_of_node[0] * 3;
1303
-
1304
- const anchor_x = state.position[anchor];
1305
- const anchor_y = state.position[anchor + 1];
1306
- const anchor_z = state.position[anchor + 2];
1307
-
1308
- const node_count = instance.node_count;
1309
-
1310
- const slack_row = instance.slack_row;
1311
- const tether_row = instance.tether_row;
1312
-
1313
- for (let node = 1; node < node_count; node++) {
1314
- const tether = tether_row[node];
1315
-
1316
- if (tether >= 0) {
1317
- target[tether * 3] = anchor_x;
1318
- target[tether * 3 + 1] = anchor_y;
1319
- target[tether * 3 + 2] = anchor_z;
1320
- }
1321
-
1322
- const slack = slack_row[node];
1323
-
1324
- if (slack >= 0) {
1325
- target[slack * 3] = animated[node * 3];
1326
- target[slack * 3 + 1] = animated[node * 3 + 1];
1327
- target[slack * 3 + 2] = animated[node * 3 + 2];
1328
- }
1329
- }
1330
- }