@woosh/meep-engine 3.22.0 → 3.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts +118 -0
  3. package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts.map +1 -0
  4. package/src/core/geom/3d/hash-grid/PointHashGrid3.js +283 -0
  5. package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
  6. package/src/core/geom/3d/shape/BoxShape3D.js +6 -17
  7. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
  8. package/src/core/geom/3d/shape/CapsuleShape3D.js +3 -14
  9. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  10. package/src/core/geom/3d/shape/CylinderShape3D.js +3 -21
  11. package/src/core/geom/3d/shape/PlaneShape3D.d.ts +105 -0
  12. package/src/core/geom/3d/shape/PlaneShape3D.d.ts.map +1 -0
  13. package/src/core/geom/3d/shape/PlaneShape3D.js +251 -0
  14. package/src/core/geom/3d/shape/SphereShape3D.js +3 -3
  15. package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
  16. package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
  17. package/src/core/geom/3d/shape/json/type_adapters.d.ts +14 -0
  18. package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
  19. package/src/core/geom/3d/shape/json/type_adapters.js +15 -0
  20. package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts +51 -0
  21. package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts.map +1 -0
  22. package/src/core/geom/3d/shape/sdf/sdf3_box.js +113 -0
  23. package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts +46 -0
  24. package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts.map +1 -0
  25. package/src/core/geom/3d/shape/sdf/sdf3_capsule.js +82 -0
  26. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts +60 -0
  27. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/sdf/sdf3_cylinder.js +126 -0
  29. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts +50 -0
  30. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts.map +1 -0
  31. package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.js +53 -0
  32. package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts +52 -0
  33. package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts.map +1 -0
  34. package/src/core/geom/3d/shape/sdf/sdf3_plane.js +61 -0
  35. package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts +41 -0
  36. package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts.map +1 -0
  37. package/src/core/geom/3d/shape/sdf/sdf3_sphere.js +71 -0
  38. package/src/core/process/ConcurrencyGate.d.ts +103 -0
  39. package/src/core/process/ConcurrencyGate.d.ts.map +1 -0
  40. package/src/core/process/ConcurrencyGate.js +207 -0
  41. package/src/engine/asset/AssetManager.d.ts +10 -0
  42. package/src/engine/asset/AssetManager.d.ts.map +1 -1
  43. package/src/engine/asset/AssetManager.js +10 -0
  44. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +30 -13
  45. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +26 -0
  46. package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -0
  47. package/src/engine/graphics3/pose/collect_entity_playbacks.js +40 -0
  48. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
  49. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +74 -101
  50. package/src/engine/physics/cloth/MEASUREMENTS.md +1287 -4
  51. package/src/engine/physics/cloth/PLAN.md +1967 -1581
  52. package/src/engine/physics/cloth/build/ClothProxy.d.ts +147 -0
  53. package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -0
  54. package/src/engine/physics/cloth/build/ClothProxy.js +207 -0
  55. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +70 -0
  56. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -0
  57. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +256 -0
  58. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +176 -0
  59. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -0
  60. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +435 -0
  61. package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts +12 -0
  62. package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts.map +1 -0
  63. package/src/engine/physics/cloth/collider/ClothColliderKind.js +38 -0
  64. package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts +120 -0
  65. package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts.map +1 -0
  66. package/src/engine/physics/cloth/collider/ClothColliderRecord.js +137 -0
  67. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +25 -0
  68. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -0
  69. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +190 -0
  70. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +44 -0
  71. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -0
  72. package/src/engine/physics/cloth/collider/cloth_collider_pack.js +173 -0
  73. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +94 -0
  74. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -0
  75. package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +275 -0
  76. package/src/engine/physics/cloth/ecs/ClothCollider.d.ts +126 -0
  77. package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -0
  78. package/src/engine/physics/cloth/ecs/ClothCollider.js +199 -0
  79. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +9 -0
  80. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts.map +1 -0
  81. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +32 -0
  82. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +114 -0
  83. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -0
  84. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +243 -0
  85. package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +66 -57
  86. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +109 -1
  87. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  88. package/src/engine/physics/cloth/ecs/ClothInstance.js +479 -363
  89. package/src/engine/physics/cloth/ecs/ClothRig.d.ts +95 -0
  90. package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -0
  91. package/src/engine/physics/cloth/ecs/ClothRig.js +140 -0
  92. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +30 -1
  93. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  94. package/src/engine/physics/cloth/ecs/ClothSystem.js +1021 -773
  95. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +90 -0
  96. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -0
  97. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +374 -0
  98. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts +32 -3
  99. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts.map +1 -1
  100. package/src/engine/physics/cloth/ecs/cloth_dynamics_library.js +283 -254
  101. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +59 -0
  102. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -0
  103. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +328 -0
  104. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +13 -0
  105. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -0
  106. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +288 -0
  107. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +58 -0
  108. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -0
  109. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +199 -0
  110. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +8 -63
  111. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  112. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +317 -645
  113. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +61 -0
  114. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  115. package/src/engine/physics/cloth/ecs/cloth_write_back.js +212 -449
  116. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +49 -0
  117. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -0
  118. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +115 -0
  119. package/src/engine/physics/cloth/playground/README.md +155 -1
  120. package/src/engine/physics/cloth/playground/collide.html +178 -0
  121. package/src/engine/physics/cloth/playground/collide_build.d.ts +95 -0
  122. package/src/engine/physics/cloth/playground/collide_build.d.ts.map +1 -0
  123. package/src/engine/physics/cloth/playground/collide_build.js +315 -0
  124. package/src/engine/physics/cloth/playground/collide_main.d.ts +2 -0
  125. package/src/engine/physics/cloth/playground/collide_main.d.ts.map +1 -0
  126. package/src/engine/physics/cloth/playground/collide_main.js +720 -0
  127. package/src/engine/physics/cloth/playground/garment.html +182 -0
  128. package/src/engine/physics/cloth/playground/garment_build.d.ts +78 -0
  129. package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -0
  130. package/src/engine/physics/cloth/playground/garment_build.js +359 -0
  131. package/src/engine/physics/cloth/playground/garment_main.d.ts +2 -0
  132. package/src/engine/physics/cloth/playground/garment_main.d.ts.map +1 -0
  133. package/src/engine/physics/cloth/playground/garment_main.js +633 -0
  134. package/src/engine/physics/cloth/solver/ClothState.d.ts +208 -5
  135. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  136. package/src/engine/physics/cloth/solver/ClothState.js +302 -8
  137. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +87 -0
  138. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -0
  139. package/src/engine/physics/cloth/solver/cloth_contact_find.js +343 -0
  140. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +112 -0
  141. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -0
  142. package/src/engine/physics/cloth/solver/cloth_self_find.js +448 -0
  143. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts +4 -1
  144. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
  145. package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +16 -1
  146. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +112 -5
  147. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  148. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +117 -6
  149. package/src/engine/physics/cloth/solver/cloth_step.d.ts +40 -9
  150. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  151. package/src/engine/physics/cloth/solver/cloth_step.js +122 -10
  152. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +95 -0
  153. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -0
  154. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +285 -0
  155. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +129 -0
  156. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -0
  157. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +501 -0
  158. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +75 -0
  159. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -0
  160. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +202 -0
  161. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  162. package/src/shade/playground/vgeo_viewer/README.md +18 -0
  163. package/src/shade/playground/vgeo_viewer/main.js +87 -50
  164. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  165. package/src/shade/renderer/buffer/table/GPUDatabase.js +191 -62
  166. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +94 -2
  167. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  168. package/src/shade/renderer/buffer/table/GPUTypedTable.js +414 -2
  169. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts +14 -0
  170. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map +1 -0
  171. package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js +13 -0
  172. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts +17 -0
  173. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts.map +1 -0
  174. package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js +116 -0
  175. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +9 -4
  176. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +843 -734
  178. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts +12 -0
  179. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.js +13 -0
  181. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +17 -0
  182. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  183. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +185 -30
  184. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +17 -0
  185. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +141 -24
  186. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +204 -64
  187. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +110 -10
  188. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +19 -0
  189. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +19 -6
  190. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
  191. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +26 -7
  192. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +173 -14
  193. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  194. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +458 -51
  195. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +49 -13
  196. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
  197. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +52 -14
  198. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts +20 -0
  199. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts.map +1 -0
  200. package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.js +19 -0
  201. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts +27 -0
  202. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts.map +1 -0
  203. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js +48 -0
  204. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts +29 -0
  205. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts.map +1 -0
  206. package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js +60 -0
  207. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +8 -1
  208. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
  209. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +32 -2
  210. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +258 -0
  211. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +1 -0
  212. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +723 -0
  213. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +49 -0
  214. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +1 -0
  215. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +50 -0
  216. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  217. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  218. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts +3 -0
  219. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts.map +1 -1
  220. package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +121 -21
  221. package/src/shade/renderer/texture/virtual/README.md +6 -4
  222. package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
  223. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +7 -0
  224. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.d.ts +3 -3
  225. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +3 -3
  226. package/src/engine/ecs/system/SystemExecutionPhase.d.ts +0 -9
  227. package/src/engine/ecs/system/SystemExecutionPhase.d.ts.map +0 -1
  228. package/src/engine/graphics/texture/virtual/NOTES.md +0 -128
  229. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts +0 -6
  230. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts.map +0 -1
  231. package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.js +0 -5
  232. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +0 -49
  233. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +0 -1
  234. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +0 -264
  235. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts +0 -44
  236. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts.map +0 -1
  237. package/src/engine/graphics/texture/virtual/VirtualTextureUsage.js +0 -228
  238. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts +0 -3
  239. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts.map +0 -1
  240. package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.js +0 -73
  241. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts +0 -14
  242. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts.map +0 -1
  243. package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.js +0 -85
  244. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts +0 -12
  245. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts.map +0 -1
  246. package/src/engine/graphics/texture/virtual/debug/UsageDebugView.js +0 -63
  247. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts +0 -12
  248. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts.map +0 -1
  249. package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.js +0 -243
  250. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts +0 -30
  251. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts.map +0 -1
  252. package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.js +0 -39
  253. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts +0 -9
  254. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts.map +0 -1
  255. package/src/engine/graphics/texture/virtual/tile/compose_finger_print.js +0 -24
  256. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts +0 -9
  257. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts.map +0 -1
  258. package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +0 -26
  259. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts +0 -11
  260. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts.map +0 -1
  261. package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.js +0 -12
  262. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts +0 -7
  263. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts.map +0 -1
  264. package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.js +0 -16
  265. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts +0 -7
  266. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts.map +0 -1
  267. package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.js +0 -35
@@ -0,0 +1,147 @@
1
+ /**
2
+ * # The built artefact a `ClothRig` points at
3
+ *
4
+ * Shared, immutable, and produced once at import. `ClothRig#proxy` holds one of
5
+ * these the way `Collider#shape` holds an `AbstractShape3D` and `ParticleEffect`
6
+ * holds a `ParticleProgram`: the component serialises the proxy's *identity*,
7
+ * and the asset pipeline wires the object up (§3.2).
8
+ *
9
+ * ## Joints by index, resolved once
10
+ *
11
+ * Every field below is an integer or a float. **No names survive into the
12
+ * runtime**, and that is §3.2's central commitment: a joint is a `Node3D` inside
13
+ * a `SceneBundleInstance` rather than an entity, so an `EntityReference` to one
14
+ * is impossible by construction, and reintroducing strings at runtime to work
15
+ * around that would be the wrong answer. {@link joint} holds indices into
16
+ * `Skin#joints`, resolved at build time from `SceneBundleInstance#nodes`.
17
+ *
18
+ * {@link joint_name} is the exception and it is build-time provenance: it is
19
+ * what a diagnostic prints when a proxy does not fit the model it was pointed
20
+ * at, and nothing on the step path reads it.
21
+ *
22
+ * ## The rigged form, which is the whole of M3
23
+ *
24
+ * §11.2 has two cases and they differ only in where the joints and the weights
25
+ * came from. **A rigged garment brings both**: an artist made 8–20 cape joints
26
+ * and skinned the cape to them, so the proxy is thin — joint indices, the
27
+ * topology connecting them, the bind pose, and `d_geo`. There is no binding
28
+ * step and no generated geometry, because the artist's skin *is* the relation.
29
+ *
30
+ * The unrigged case generates a joint set and a skin for a garment that has
31
+ * neither, and its extra data — the render mesh's generated weights and
32
+ * inverse-bind matrices — is not in this class yet. §11.3 step 9 and §18's open
33
+ * joint-budget question are what would add it.
34
+ *
35
+ * ## The bind pose is baked, not read
36
+ *
37
+ * {@link rest_position} and {@link rest_rotation} are the joints' **local**
38
+ * transforms at bind time, carried in the proxy rather than read from the
39
+ * instance at seed. Reading them from the instance works exactly once: cloth
40
+ * takes ownership of `Node3D.transform_local` the moment it starts writing, so
41
+ * a re-seed of a live garment would read the shape the solver left behind and
42
+ * call it the authored pose. M1 solved the same problem for the entity path with
43
+ * a per-seed capture (`remember_rest_pose`); a proxy can do better, because the
44
+ * bind pose is build data and does not have to be remembered at all.
45
+ *
46
+ * @author Alex Goldring
47
+ * @copyright Company Named Limited (c) 2026
48
+ */
49
+ export class ClothProxy {
50
+ /**
51
+ * @param {object} description
52
+ * @param {string} [description.name]
53
+ * @param {Int32Array|number[]} description.joint
54
+ * @param {Int32Array|number[]} description.joint_parent
55
+ * @param {number} description.anchor
56
+ * @param {Float32Array|number[]} description.rest_position
57
+ * @param {Float32Array|number[]} description.rest_rotation
58
+ * @param {string[]} [description.joint_name]
59
+ * @returns {ClothProxy}
60
+ */
61
+ static from({ name, joint, joint_parent, anchor, rest_position, rest_rotation, joint_name }: {
62
+ name?: string;
63
+ joint: Int32Array | number[];
64
+ joint_parent: Int32Array | number[];
65
+ anchor: number;
66
+ rest_position: Float32Array | number[];
67
+ rest_rotation: Float32Array | number[];
68
+ joint_name?: string[];
69
+ }): ClothProxy;
70
+ /**
71
+ * Name, for diagnostics and for a tool listing the proxies an asset carries.
72
+ * @type {string}
73
+ */
74
+ name: string;
75
+ /**
76
+ * How many joints this proxy simulates. The anchor is **not** one of them.
77
+ * @type {number}
78
+ */
79
+ joint_count: number;
80
+ /**
81
+ * Index into `Skin#joints` for each simulated joint.
82
+ * @type {Int32Array}
83
+ */
84
+ joint: Int32Array;
85
+ /**
86
+ * Parent of each simulated joint, as an index into this proxy's own joint
87
+ * list, or `-1` for a joint whose parent is the anchor.
88
+ *
89
+ * A proxy's joints are stored **parents before children**, so one forward
90
+ * pass composes the whole chain — the same ordering `cloth_seed_subtree`
91
+ * gets from a breadth-first walk, and for the same reason.
92
+ * @type {Int32Array}
93
+ */
94
+ joint_parent: Int32Array;
95
+ /**
96
+ * Index into `Skin#joints` of the joint the garment hangs from.
97
+ *
98
+ * It is not simulated, it is the cloth's local frame, and its `d_geo` is
99
+ * zero — which is what makes §3.4's derived quantities collapse onto it
100
+ * correctly without a second mechanism saying "this one is pinned".
101
+ *
102
+ * For a rigged garment the fixed set *is* the anchor, and it falls out of
103
+ * the topology rather than out of skin weights: a joint whose parent is
104
+ * outside the proxy is a joint the garment hangs from. §11.3's step 5 —
105
+ * "particles whose skin weights are dominated by joints outside the
106
+ * garment's own joint set are pinned" — is the **unrigged** rule, where the
107
+ * particles are mesh vertices and there is no joint tree to read.
108
+ * @type {number}
109
+ */
110
+ anchor: number;
111
+ /**
112
+ * Each joint's bind-pose local translation, 3 per joint.
113
+ * @type {Float32Array}
114
+ */
115
+ rest_position: Float32Array;
116
+ /**
117
+ * Each joint's bind-pose local rotation, 4 per joint.
118
+ * @type {Float32Array}
119
+ */
120
+ rest_rotation: Float32Array;
121
+ /**
122
+ * Names, in {@link joint} order, then the anchor's last. Build-time
123
+ * provenance; nothing on the step path reads it.
124
+ * @type {string[]}
125
+ */
126
+ joint_name: string[];
127
+ /**
128
+ * Whether this proxy can address the skin it is about to be used with.
129
+ *
130
+ * A proxy is built against one model and a `ClothRig` points at whichever
131
+ * one the entity is showing, so the two can disagree — a model swap, an
132
+ * asset rebuilt with a joint removed, a proxy pointed at the wrong skin
133
+ * ordinal. Every one of those is an authoring mistake rather than a runtime
134
+ * condition, and this is what turns it into a message naming the joint
135
+ * instead of a garment simulating somebody's spine.
136
+ *
137
+ * @param {Skin} skin
138
+ * @returns {string} empty when the proxy fits, otherwise what is wrong
139
+ */
140
+ checkAgainst(skin: Skin): string;
141
+ /**
142
+ * @readonly
143
+ * @type {boolean}
144
+ */
145
+ readonly isClothProxy: boolean;
146
+ }
147
+ //# sourceMappingURL=ClothProxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClothProxy.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/ClothProxy.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH;IAmEI;;;;;;;;;;OAUG;IACH;QATgC,IAAI,GAAzB,MAAM;QAC2B,KAAK,EAAtC,UAAU,GAAC,MAAM,EAAE;QACc,YAAY,EAA7C,UAAU,GAAC,MAAM,EAAE;QACC,MAAM,EAA1B,MAAM;QAC6B,aAAa,EAAhD,YAAY,GAAC,MAAM,EAAE;QACc,aAAa,EAAhD,YAAY,GAAC,MAAM,EAAE;QACE,UAAU,GAAjC,MAAM,EAAE;QACN,UAAU,CAuCtB;IAjHD;;;OAGG;IACH,MAFU,MAAM,CAEN;IAEV;;;OAGG;IACH,aAFU,MAAM,CAEA;IAEhB;;;OAGG;IACH,OAFU,UAAU,CAEP;IAEb;;;;;;;;OAQG;IACH,cAFU,UAAU,CAEA;IAEpB;;;;;;;;;;;;;;OAcG;IACH,QAFU,MAAM,CAEJ;IAEZ;;;OAGG;IACH,eAFU,YAAY,CAED;IAErB;;;OAGG;IACH,eAFU,YAAY,CAED;IAErB;;;;OAIG;IACH,YAFU,MAAM,EAAE,CAEF;IAoDhB;;;;;;;;;;;;OAYG;IACH,0BAFa,MAAM,CAqBlB;IAGL;;;OAGG;IACH,uBAFU,OAAO,CAEgB;CANhC"}
@@ -0,0 +1,207 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+
3
+ /**
4
+ * # The built artefact a `ClothRig` points at
5
+ *
6
+ * Shared, immutable, and produced once at import. `ClothRig#proxy` holds one of
7
+ * these the way `Collider#shape` holds an `AbstractShape3D` and `ParticleEffect`
8
+ * holds a `ParticleProgram`: the component serialises the proxy's *identity*,
9
+ * and the asset pipeline wires the object up (§3.2).
10
+ *
11
+ * ## Joints by index, resolved once
12
+ *
13
+ * Every field below is an integer or a float. **No names survive into the
14
+ * runtime**, and that is §3.2's central commitment: a joint is a `Node3D` inside
15
+ * a `SceneBundleInstance` rather than an entity, so an `EntityReference` to one
16
+ * is impossible by construction, and reintroducing strings at runtime to work
17
+ * around that would be the wrong answer. {@link joint} holds indices into
18
+ * `Skin#joints`, resolved at build time from `SceneBundleInstance#nodes`.
19
+ *
20
+ * {@link joint_name} is the exception and it is build-time provenance: it is
21
+ * what a diagnostic prints when a proxy does not fit the model it was pointed
22
+ * at, and nothing on the step path reads it.
23
+ *
24
+ * ## The rigged form, which is the whole of M3
25
+ *
26
+ * §11.2 has two cases and they differ only in where the joints and the weights
27
+ * came from. **A rigged garment brings both**: an artist made 8–20 cape joints
28
+ * and skinned the cape to them, so the proxy is thin — joint indices, the
29
+ * topology connecting them, the bind pose, and `d_geo`. There is no binding
30
+ * step and no generated geometry, because the artist's skin *is* the relation.
31
+ *
32
+ * The unrigged case generates a joint set and a skin for a garment that has
33
+ * neither, and its extra data — the render mesh's generated weights and
34
+ * inverse-bind matrices — is not in this class yet. §11.3 step 9 and §18's open
35
+ * joint-budget question are what would add it.
36
+ *
37
+ * ## The bind pose is baked, not read
38
+ *
39
+ * {@link rest_position} and {@link rest_rotation} are the joints' **local**
40
+ * transforms at bind time, carried in the proxy rather than read from the
41
+ * instance at seed. Reading them from the instance works exactly once: cloth
42
+ * takes ownership of `Node3D.transform_local` the moment it starts writing, so
43
+ * a re-seed of a live garment would read the shape the solver left behind and
44
+ * call it the authored pose. M1 solved the same problem for the entity path with
45
+ * a per-seed capture (`remember_rest_pose`); a proxy can do better, because the
46
+ * bind pose is build data and does not have to be remembered at all.
47
+ *
48
+ * @author Alex Goldring
49
+ * @copyright Company Named Limited (c) 2026
50
+ */
51
+ export class ClothProxy {
52
+
53
+ /**
54
+ * Name, for diagnostics and for a tool listing the proxies an asset carries.
55
+ * @type {string}
56
+ */
57
+ name = "";
58
+
59
+ /**
60
+ * How many joints this proxy simulates. The anchor is **not** one of them.
61
+ * @type {number}
62
+ */
63
+ joint_count = 0;
64
+
65
+ /**
66
+ * Index into `Skin#joints` for each simulated joint.
67
+ * @type {Int32Array}
68
+ */
69
+ joint = null;
70
+
71
+ /**
72
+ * Parent of each simulated joint, as an index into this proxy's own joint
73
+ * list, or `-1` for a joint whose parent is the anchor.
74
+ *
75
+ * A proxy's joints are stored **parents before children**, so one forward
76
+ * pass composes the whole chain — the same ordering `cloth_seed_subtree`
77
+ * gets from a breadth-first walk, and for the same reason.
78
+ * @type {Int32Array}
79
+ */
80
+ joint_parent = null;
81
+
82
+ /**
83
+ * Index into `Skin#joints` of the joint the garment hangs from.
84
+ *
85
+ * It is not simulated, it is the cloth's local frame, and its `d_geo` is
86
+ * zero — which is what makes §3.4's derived quantities collapse onto it
87
+ * correctly without a second mechanism saying "this one is pinned".
88
+ *
89
+ * For a rigged garment the fixed set *is* the anchor, and it falls out of
90
+ * the topology rather than out of skin weights: a joint whose parent is
91
+ * outside the proxy is a joint the garment hangs from. §11.3's step 5 —
92
+ * "particles whose skin weights are dominated by joints outside the
93
+ * garment's own joint set are pinned" — is the **unrigged** rule, where the
94
+ * particles are mesh vertices and there is no joint tree to read.
95
+ * @type {number}
96
+ */
97
+ anchor = -1;
98
+
99
+ /**
100
+ * Each joint's bind-pose local translation, 3 per joint.
101
+ * @type {Float32Array}
102
+ */
103
+ rest_position = null;
104
+
105
+ /**
106
+ * Each joint's bind-pose local rotation, 4 per joint.
107
+ * @type {Float32Array}
108
+ */
109
+ rest_rotation = null;
110
+
111
+ /**
112
+ * Names, in {@link joint} order, then the anchor's last. Build-time
113
+ * provenance; nothing on the step path reads it.
114
+ * @type {string[]}
115
+ */
116
+ joint_name = [];
117
+
118
+ /**
119
+ * @param {object} description
120
+ * @param {string} [description.name]
121
+ * @param {Int32Array|number[]} description.joint
122
+ * @param {Int32Array|number[]} description.joint_parent
123
+ * @param {number} description.anchor
124
+ * @param {Float32Array|number[]} description.rest_position
125
+ * @param {Float32Array|number[]} description.rest_rotation
126
+ * @param {string[]} [description.joint_name]
127
+ * @returns {ClothProxy}
128
+ */
129
+ static from({
130
+ name = "",
131
+ joint,
132
+ joint_parent,
133
+ anchor,
134
+ rest_position,
135
+ rest_rotation,
136
+ joint_name = []
137
+ }) {
138
+ const joint_count = joint.length;
139
+
140
+ assert.equal(joint_parent.length, joint_count, 'joint_parent.length');
141
+ assert.equal(rest_position.length, joint_count * 3, 'rest_position.length');
142
+ assert.equal(rest_rotation.length, joint_count * 4, 'rest_rotation.length');
143
+ assert.isNonNegativeInteger(anchor, 'anchor');
144
+
145
+ for (let i = 0; i < joint_count; i++) {
146
+ // Parents before children: the composition down the chain is one
147
+ // forward pass and there is nowhere for a cycle to hide.
148
+ assert.ok(
149
+ joint_parent[i] < i,
150
+ `ClothProxy joint ${i} has parent ${joint_parent[i]}, which is not before it`
151
+ );
152
+ }
153
+
154
+ const r = new ClothProxy();
155
+
156
+ r.name = name;
157
+ r.joint_count = joint_count;
158
+ r.joint = Int32Array.from(joint);
159
+ r.joint_parent = Int32Array.from(joint_parent);
160
+ r.anchor = anchor;
161
+ r.rest_position = Float32Array.from(rest_position);
162
+ r.rest_rotation = Float32Array.from(rest_rotation);
163
+ r.joint_name = joint_name.slice();
164
+
165
+ return r;
166
+ }
167
+
168
+ /**
169
+ * Whether this proxy can address the skin it is about to be used with.
170
+ *
171
+ * A proxy is built against one model and a `ClothRig` points at whichever
172
+ * one the entity is showing, so the two can disagree — a model swap, an
173
+ * asset rebuilt with a joint removed, a proxy pointed at the wrong skin
174
+ * ordinal. Every one of those is an authoring mistake rather than a runtime
175
+ * condition, and this is what turns it into a message naming the joint
176
+ * instead of a garment simulating somebody's spine.
177
+ *
178
+ * @param {Skin} skin
179
+ * @returns {string} empty when the proxy fits, otherwise what is wrong
180
+ */
181
+ checkAgainst(skin) {
182
+ if (skin === undefined || skin === null) {
183
+ return `${this.name}: no skin`;
184
+ }
185
+
186
+ const count = skin.joints.length;
187
+
188
+ if (this.anchor >= count) {
189
+ return `${this.name}: anchor is joint ${this.anchor} of a skin with ${count}`;
190
+ }
191
+
192
+ for (let i = 0; i < this.joint_count; i++) {
193
+ if (this.joint[i] >= count) {
194
+ return `${this.name}: joint ${i} (${this.joint_name[i] ?? '?'})`
195
+ + ` is index ${this.joint[i]} of a skin with ${count}`;
196
+ }
197
+ }
198
+
199
+ return "";
200
+ }
201
+ }
202
+
203
+ /**
204
+ * @readonly
205
+ * @type {boolean}
206
+ */
207
+ ClothProxy.prototype.isClothProxy = true;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * # Building a proxy for a garment that is already rigged
3
+ *
4
+ * §11.3 lists ten steps and says that for a rigged garment "only steps 5–8 run,
5
+ * over the joint graph rather than a mesh". This is those steps: derive the
6
+ * fixed set, bake the bind pose, resolve names to indices, and validate. Steps
7
+ * 1–4 are mesh work a rigged garment does not need — the artist's joints *are*
8
+ * the particle set and the artist's skin *is* the binding — and steps 9 and 10's
9
+ * mesh half belong to the unrigged path.
10
+ *
11
+ * The colouring step (7) is absent for a reason rather than an omission: the
12
+ * cloth's own row builder colours the particle graph inside
13
+ * `ClothState.from`, and it does that for the entity path too. Baking a
14
+ * permutation into the proxy would be a second answer to a question that
15
+ * already has one, and it would have to agree with `cloth_color_particles`
16
+ * exactly or the rows would name the wrong particles.
17
+ *
18
+ * ## The fixed set falls out of the topology
19
+ *
20
+ * A joint whose parent is outside the named set is a joint the garment hangs
21
+ * from, and **every one of them must hang from the same bone** — that bone is
22
+ * the anchor. §11.3 step 5 derives the fixed set from *skin weights* instead,
23
+ * which is the right rule for the unrigged path, where the particles are mesh
24
+ * vertices and there is no joint tree to read. For a rigged garment the tree is
25
+ * the answer and the weights are not needed.
26
+ *
27
+ * Several chains from one bone are one garment and are the point: §3.1's "put
28
+ * `Cloth` on a node with several chains under it and you get all of them in one
29
+ * coupled solve" is exactly a cape's four strips off one shoulder. Chains from
30
+ * *different* bones are refused, because there is then no single frame to
31
+ * simulate in and no single fixed set — that is two garments, and nothing stops
32
+ * a caller building two proxies.
33
+ *
34
+ * @author Alex Goldring
35
+ * @copyright Company Named Limited (c) 2026
36
+ */
37
+ /**
38
+ * @typedef {object} ClothProxyBuildResult
39
+ * @property {ClothProxy|null} proxy `null` when the build refused
40
+ * @property {string} error empty when it did not
41
+ */
42
+ /**
43
+ * Build a proxy from the joints of a skin, named.
44
+ *
45
+ * The names are the only place a name appears anywhere in the system (§11.3
46
+ * step 8) and they do not survive: what comes back holds indices into
47
+ * `skin.joints`.
48
+ *
49
+ * @param {Skin} skin the instance's skin, or the asset's — the joint *order* is
50
+ * what is read, and instantiation preserves it
51
+ * @param {string[]} joint_names the garment's own joints, in any order
52
+ * @param {object} [options]
53
+ * @param {string} [options.name]
54
+ * @returns {ClothProxyBuildResult}
55
+ */
56
+ export function cloth_proxy_from_joints(skin: Skin, joint_names: string[], { name }?: {
57
+ name?: string;
58
+ }): ClothProxyBuildResult;
59
+ export type ClothProxyBuildResult = {
60
+ /**
61
+ * `null` when the build refused
62
+ */
63
+ proxy: ClothProxy | null;
64
+ /**
65
+ * empty when it did not
66
+ */
67
+ error: string;
68
+ };
69
+ import { ClothProxy } from "./ClothProxy.js";
70
+ //# sourceMappingURL=cloth_proxy_from_joints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloth_proxy_from_joints.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/cloth_proxy_from_joints.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;GAIG;AAEH;;;;;;;;;;;;;GAaG;AACH,iEALW,MAAM,EAAE;IAES,IAAI,GAArB,MAAM;IACJ,qBAAqB,CAyLjC;;;;;WAzMa,UAAU,GAAC,IAAI;;;;WACf,MAAM;;2BA1CO,iBAAiB"}
@@ -0,0 +1,256 @@
1
+ import {
2
+ TRANSFORM64_ROTATION_OFFSET,
3
+ TRANSFORM64_TRANSLATION_OFFSET
4
+ } from "../../../ecs/transform/Transform64.js";
5
+ import { ClothProxy } from "./ClothProxy.js";
6
+
7
+ /**
8
+ * # Building a proxy for a garment that is already rigged
9
+ *
10
+ * §11.3 lists ten steps and says that for a rigged garment "only steps 5–8 run,
11
+ * over the joint graph rather than a mesh". This is those steps: derive the
12
+ * fixed set, bake the bind pose, resolve names to indices, and validate. Steps
13
+ * 1–4 are mesh work a rigged garment does not need — the artist's joints *are*
14
+ * the particle set and the artist's skin *is* the binding — and steps 9 and 10's
15
+ * mesh half belong to the unrigged path.
16
+ *
17
+ * The colouring step (7) is absent for a reason rather than an omission: the
18
+ * cloth's own row builder colours the particle graph inside
19
+ * `ClothState.from`, and it does that for the entity path too. Baking a
20
+ * permutation into the proxy would be a second answer to a question that
21
+ * already has one, and it would have to agree with `cloth_color_particles`
22
+ * exactly or the rows would name the wrong particles.
23
+ *
24
+ * ## The fixed set falls out of the topology
25
+ *
26
+ * A joint whose parent is outside the named set is a joint the garment hangs
27
+ * from, and **every one of them must hang from the same bone** — that bone is
28
+ * the anchor. §11.3 step 5 derives the fixed set from *skin weights* instead,
29
+ * which is the right rule for the unrigged path, where the particles are mesh
30
+ * vertices and there is no joint tree to read. For a rigged garment the tree is
31
+ * the answer and the weights are not needed.
32
+ *
33
+ * Several chains from one bone are one garment and are the point: §3.1's "put
34
+ * `Cloth` on a node with several chains under it and you get all of them in one
35
+ * coupled solve" is exactly a cape's four strips off one shoulder. Chains from
36
+ * *different* bones are refused, because there is then no single frame to
37
+ * simulate in and no single fixed set — that is two garments, and nothing stops
38
+ * a caller building two proxies.
39
+ *
40
+ * @author Alex Goldring
41
+ * @copyright Company Named Limited (c) 2026
42
+ */
43
+
44
+ /**
45
+ * @typedef {object} ClothProxyBuildResult
46
+ * @property {ClothProxy|null} proxy `null` when the build refused
47
+ * @property {string} error empty when it did not
48
+ */
49
+
50
+ /**
51
+ * Build a proxy from the joints of a skin, named.
52
+ *
53
+ * The names are the only place a name appears anywhere in the system (§11.3
54
+ * step 8) and they do not survive: what comes back holds indices into
55
+ * `skin.joints`.
56
+ *
57
+ * @param {Skin} skin the instance's skin, or the asset's — the joint *order* is
58
+ * what is read, and instantiation preserves it
59
+ * @param {string[]} joint_names the garment's own joints, in any order
60
+ * @param {object} [options]
61
+ * @param {string} [options.name]
62
+ * @returns {ClothProxyBuildResult}
63
+ */
64
+ export function cloth_proxy_from_joints(skin, joint_names, { name = "" } = {}) {
65
+ if (skin === undefined || skin === null) {
66
+ return refuse(`${name}: no skin`);
67
+ }
68
+
69
+ const joints = skin.joints;
70
+
71
+ /** @type {Map<Node3D, number>} node → index in `skin.joints` */
72
+ const index_of = new Map();
73
+
74
+ for (let i = 0; i < joints.length; i++) {
75
+ index_of.set(joints[i], i);
76
+ }
77
+
78
+ /** @type {Map<string, Node3D>} */
79
+ const by_name = new Map();
80
+
81
+ for (let i = 0; i < joints.length; i++) {
82
+ const joint = joints[i];
83
+
84
+ if (joint.name !== "" && !by_name.has(joint.name)) {
85
+ by_name.set(joint.name, joint);
86
+ }
87
+ }
88
+
89
+ /** @type {Set<Node3D>} the garment's own joints */
90
+ const members = new Set();
91
+
92
+ for (const joint_name of joint_names) {
93
+ const node = by_name.get(joint_name);
94
+
95
+ if (node === undefined) {
96
+ return refuse(`${name}: the skin has no joint named "${joint_name}"`);
97
+ }
98
+
99
+ if (members.has(node)) {
100
+ return refuse(`${name}: "${joint_name}" is named twice`);
101
+ }
102
+
103
+ members.add(node);
104
+ }
105
+
106
+ if (members.size === 0) {
107
+ return refuse(`${name}: no joints`);
108
+ }
109
+
110
+ // Roots: members whose parent is not a member. They all have to hang from
111
+ // the same bone, and that bone is the anchor.
112
+ /** @type {Node3D[]} */
113
+ const roots = [];
114
+
115
+ for (const node of members) {
116
+ if (node.parent === null || !members.has(node.parent)) {
117
+ roots.push(node);
118
+ }
119
+ }
120
+
121
+ const anchor_node = roots[0].parent;
122
+
123
+ if (anchor_node === null) {
124
+ return refuse(`${name}: "${roots[0].name}" has no parent, so the garment has nothing to hang from`);
125
+ }
126
+
127
+ for (const root of roots) {
128
+ if (root.parent !== anchor_node) {
129
+ return refuse(
130
+ `${name}: "${roots[0].name}" hangs from "${anchor_node.name}" and "${root.name}" hangs`
131
+ + ` from "${root.parent === null ? 'nothing' : root.parent.name}" — a garment hangs from one`
132
+ + ` anchor, and chains from different bones are two garments`
133
+ );
134
+ }
135
+ }
136
+
137
+ const anchor = index_of.get(anchor_node);
138
+
139
+ if (anchor === undefined) {
140
+ return refuse(
141
+ `${name}: "${roots[0].name}" hangs from "${anchor_node.name}", which is not a joint of this skin`
142
+ );
143
+ }
144
+
145
+ // Parents before children, so one forward pass composes the chain.
146
+ /** @type {Node3D[]} */
147
+ const ordered = [];
148
+
149
+ /** @type {Map<Node3D, number>} */
150
+ const position = new Map();
151
+
152
+ // Breadth-first from every root at once, which keeps parents before
153
+ // children across the whole forest rather than only within one chain.
154
+ const queue = roots.slice();
155
+
156
+ while (queue.length > 0) {
157
+ const node = queue.shift();
158
+
159
+ position.set(node, ordered.length);
160
+ ordered.push(node);
161
+
162
+ for (const child of node.children) {
163
+ if (members.has(child)) {
164
+ queue.push(child);
165
+ }
166
+ }
167
+ }
168
+
169
+ if (ordered.length !== members.size) {
170
+ const stranded = [];
171
+
172
+ for (const node of members) {
173
+ if (!position.has(node)) {
174
+ stranded.push(node.name);
175
+ }
176
+ }
177
+
178
+ return refuse(
179
+ `${name}: ${stranded.length} joints are unreachable from the anchor (${stranded.join(', ')})`
180
+ );
181
+ }
182
+
183
+ const joint_count = ordered.length;
184
+
185
+ const joint = new Int32Array(joint_count);
186
+ const joint_parent = new Int32Array(joint_count);
187
+
188
+ const rest_position = new Float32Array(joint_count * 3);
189
+ const rest_rotation = new Float32Array(joint_count * 4);
190
+
191
+ const joint_name = [];
192
+
193
+ for (let i = 0; i < joint_count; i++) {
194
+ const node = ordered[i];
195
+
196
+ const resolved = index_of.get(node);
197
+
198
+ if (resolved === undefined) {
199
+ return refuse(`${name}: "${node.name}" is not a joint of this skin`);
200
+ }
201
+
202
+ joint[i] = resolved;
203
+ joint_parent[i] = members.has(node.parent) ? position.get(node.parent) : -1;
204
+
205
+ joint_name.push(node.name);
206
+
207
+ const local = node.transform_local;
208
+
209
+ rest_position[i * 3] = local[TRANSFORM64_TRANSLATION_OFFSET];
210
+ rest_position[i * 3 + 1] = local[TRANSFORM64_TRANSLATION_OFFSET + 1];
211
+ rest_position[i * 3 + 2] = local[TRANSFORM64_TRANSLATION_OFFSET + 2];
212
+
213
+ rest_rotation[i * 4] = local[TRANSFORM64_ROTATION_OFFSET];
214
+ rest_rotation[i * 4 + 1] = local[TRANSFORM64_ROTATION_OFFSET + 1];
215
+ rest_rotation[i * 4 + 2] = local[TRANSFORM64_ROTATION_OFFSET + 2];
216
+ rest_rotation[i * 4 + 3] = local[TRANSFORM64_ROTATION_OFFSET + 3];
217
+
218
+ // A joint at no distance from its parent is a zero-length link, which
219
+ // has no direction for a stretch row's gradient and no rest length for
220
+ // it to hold. §7 P8 rejects it at build time rather than tolerating it
221
+ // at runtime, which is what "build-time validation, not runtime
222
+ // tolerance" means. The root is included: its parent is the anchor.
223
+ const length = Math.hypot(
224
+ rest_position[i * 3], rest_position[i * 3 + 1], rest_position[i * 3 + 2]
225
+ );
226
+
227
+ if (length <= 0) {
228
+ return refuse(
229
+ `${name}: "${node.name}" sits exactly on its parent, so the link between them`
230
+ + ` has no length and no direction`
231
+ );
232
+ }
233
+ }
234
+
235
+ return {
236
+ proxy: ClothProxy.from({
237
+ name,
238
+ joint,
239
+ joint_parent,
240
+ anchor,
241
+ rest_position,
242
+ rest_rotation,
243
+ joint_name
244
+ }),
245
+ error: ""
246
+ };
247
+ }
248
+
249
+ /**
250
+ * @param {string} error
251
+ * @returns {ClothProxyBuildResult}
252
+ * @private
253
+ */
254
+ function refuse(error) {
255
+ return { proxy: null, error };
256
+ }