@woosh/meep-engine 3.43.0 → 3.45.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 (466) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/editor/prototypeEditorShell.d.ts.map +1 -1
  3. package/editor/prototypeEditorShell.js +4 -2
  4. package/package.json +1 -1
  5. package/src/core/collection/array/array_sort_insertion_ascending.d.ts +19 -0
  6. package/src/core/collection/array/array_sort_insertion_ascending.d.ts.map +1 -0
  7. package/src/core/collection/array/array_sort_insertion_ascending.js +31 -0
  8. package/src/core/geom/3d/aabb/aabb3_array_round_outward_f32.d.ts +27 -0
  9. package/src/core/geom/3d/aabb/aabb3_array_round_outward_f32.d.ts.map +1 -0
  10. package/src/core/geom/3d/aabb/aabb3_array_round_outward_f32.js +39 -0
  11. package/src/core/geom/3d/aabb/aabb3_from_v3_array.d.ts.map +1 -1
  12. package/src/core/geom/3d/aabb/aabb3_from_v3_array.js +4 -1
  13. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts +2 -2
  14. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -1
  15. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +1 -1
  16. package/src/core/geom/3d/pose/pose3_apply_inverse_point_array.d.ts +21 -0
  17. package/src/core/geom/3d/pose/pose3_apply_inverse_point_array.d.ts.map +1 -0
  18. package/src/core/geom/3d/pose/pose3_apply_inverse_point_array.js +36 -0
  19. package/src/core/geom/3d/pose/pose3_apply_point_array.d.ts +25 -0
  20. package/src/core/geom/3d/pose/pose3_apply_point_array.d.ts.map +1 -0
  21. package/src/core/geom/3d/pose/pose3_apply_point_array.js +40 -0
  22. package/src/core/geom/3d/pose/pose3_compose_array.d.ts +28 -0
  23. package/src/core/geom/3d/pose/pose3_compose_array.d.ts.map +1 -0
  24. package/src/core/geom/3d/pose/pose3_compose_array.js +41 -0
  25. package/src/core/geom/3d/pose/pose3_to_local_array.d.ts +29 -0
  26. package/src/core/geom/3d/pose/pose3_to_local_array.d.ts.map +1 -0
  27. package/src/core/geom/3d/pose/pose3_to_local_array.js +42 -0
  28. package/src/core/geom/3d/quaternion/quat3_clamp_angle_array.d.ts +28 -0
  29. package/src/core/geom/3d/quaternion/quat3_clamp_angle_array.d.ts.map +1 -0
  30. package/src/core/geom/3d/quaternion/quat3_clamp_angle_array.js +66 -0
  31. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_left_array.d.ts +22 -0
  32. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_left_array.d.ts.map +1 -0
  33. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_left_array.js +30 -0
  34. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_right_array.d.ts +23 -0
  35. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_right_array.d.ts.map +1 -0
  36. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_right_array.js +31 -0
  37. package/src/core/geom/3d/triangle/tri3_compute_normal_area_weighted_array.d.ts +26 -0
  38. package/src/core/geom/3d/triangle/tri3_compute_normal_area_weighted_array.d.ts.map +1 -0
  39. package/src/core/geom/3d/triangle/tri3_compute_normal_area_weighted_array.js +46 -0
  40. package/src/core/geom/vec3/v3_distance_array.d.ts +11 -0
  41. package/src/core/geom/vec3/v3_distance_array.d.ts.map +1 -0
  42. package/src/core/geom/vec3/v3_distance_array.js +19 -0
  43. package/src/core/geom/vec3/v3_normalize_array_checked.d.ts +23 -0
  44. package/src/core/geom/vec3/v3_normalize_array_checked.d.ts.map +1 -0
  45. package/src/core/geom/vec3/v3_normalize_array_checked.js +49 -0
  46. package/src/core/geom/vec3/v3_reject_array.d.ts +15 -0
  47. package/src/core/geom/vec3/v3_reject_array.d.ts.map +1 -0
  48. package/src/core/geom/vec3/v3_reject_array.js +32 -0
  49. package/src/core/graph/csr/csr_build_incidence.d.ts +26 -0
  50. package/src/core/graph/csr/csr_build_incidence.d.ts.map +1 -0
  51. package/src/core/graph/csr/csr_build_incidence.js +71 -0
  52. package/src/core/graph/csr/csr_graph_color_greedy.d.ts +25 -0
  53. package/src/core/graph/csr/csr_graph_color_greedy.d.ts.map +1 -0
  54. package/src/core/graph/csr/csr_graph_color_greedy.js +83 -0
  55. package/src/core/graph/csr/csr_graph_validate_coloring.d.ts +15 -0
  56. package/src/core/graph/csr/csr_graph_validate_coloring.d.ts.map +1 -0
  57. package/src/core/graph/csr/csr_graph_validate_coloring.js +33 -0
  58. package/src/core/math/linalg/matrix_symmetric_3x3_solve_ldlt.d.ts +26 -0
  59. package/src/core/math/linalg/matrix_symmetric_3x3_solve_ldlt.d.ts.map +1 -0
  60. package/src/core/math/linalg/matrix_symmetric_3x3_solve_ldlt.js +71 -0
  61. package/src/core/math/max2_exact.d.ts +15 -0
  62. package/src/core/math/max2_exact.d.ts.map +1 -0
  63. package/src/core/math/max2_exact.js +30 -0
  64. package/src/core/math/min2_exact.d.ts +20 -0
  65. package/src/core/math/min2_exact.d.ts.map +1 -0
  66. package/src/core/math/min2_exact.js +35 -0
  67. package/src/engine/Clock.d.ts +15 -0
  68. package/src/engine/Clock.d.ts.map +1 -1
  69. package/src/engine/Clock.js +29 -5
  70. package/src/engine/Engine.d.ts.map +1 -1
  71. package/src/engine/Engine.js +11 -19
  72. package/src/engine/ecs/ik/IKGoalSystem.d.ts.map +1 -1
  73. package/src/engine/ecs/ik/IKGoalSystem.js +3 -19
  74. package/src/engine/ecs/parent/parent_entity_model_of.d.ts +17 -0
  75. package/src/engine/ecs/parent/parent_entity_model_of.d.ts.map +1 -0
  76. package/src/engine/ecs/parent/parent_entity_model_of.js +26 -0
  77. package/src/engine/ecs/transform/t64_read_pose_array.d.ts +15 -0
  78. package/src/engine/ecs/transform/t64_read_pose_array.d.ts.map +1 -0
  79. package/src/engine/ecs/transform/t64_read_pose_array.js +24 -0
  80. package/src/engine/graphics/ecs/mesh-v2/MeshSkin.d.ts +57 -0
  81. package/src/engine/graphics/ecs/mesh-v2/MeshSkin.d.ts.map +1 -0
  82. package/src/engine/graphics/ecs/mesh-v2/MeshSkin.js +84 -0
  83. package/src/engine/graphics/geometry/geometry_construct_triangle_normal_area_weighted.d.ts +2 -0
  84. package/src/engine/graphics/geometry/geometry_construct_triangle_normal_area_weighted.d.ts.map +1 -1
  85. package/src/engine/graphics/geometry/geometry_construct_triangle_normal_area_weighted.js +10 -21
  86. package/src/engine/graphics3/AnimationGraphSystem.d.ts +28 -13
  87. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
  88. package/src/engine/graphics3/AnimationGraphSystem.js +107 -64
  89. package/src/engine/graphics3/AnimationSystem.d.ts +29 -15
  90. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
  91. package/src/engine/graphics3/AnimationSystem.js +112 -61
  92. package/src/engine/graphics3/MeshSkinSystem.d.ts +56 -0
  93. package/src/engine/graphics3/MeshSkinSystem.d.ts.map +1 -0
  94. package/src/engine/graphics3/MeshSkinSystem.js +317 -0
  95. package/src/engine/graphics3/MeshSystem.d.ts +15 -35
  96. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  97. package/src/engine/graphics3/MeshSystem.js +21 -298
  98. package/src/engine/graphics3/RowChains.d.ts +76 -0
  99. package/src/engine/graphics3/RowChains.d.ts.map +1 -0
  100. package/src/engine/graphics3/RowChains.js +347 -0
  101. package/src/engine/graphics3/ShadedGeometrySystem.d.ts +6 -0
  102. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  103. package/src/engine/graphics3/ShadedGeometrySystem.js +329 -309
  104. package/src/engine/graphics3/animation/AnimationClips.d.ts +56 -0
  105. package/src/engine/graphics3/animation/AnimationClips.d.ts.map +1 -0
  106. package/src/engine/graphics3/animation/AnimationClips.js +79 -0
  107. package/src/engine/graphics3/animation/ClipListPlayer.d.ts +13 -14
  108. package/src/engine/graphics3/animation/ClipListPlayer.d.ts.map +1 -1
  109. package/src/engine/graphics3/animation/ClipListPlayer.js +262 -263
  110. package/src/engine/graphics3/animation/write_clip_playback.d.ts +3 -3
  111. package/src/engine/graphics3/animation/write_clip_playback.js +4 -4
  112. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts +5 -0
  113. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
  114. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +80 -75
  115. package/src/engine/graphics3/prefab/PrefabInstance.d.ts +4 -24
  116. package/src/engine/graphics3/prefab/PrefabInstance.d.ts.map +1 -1
  117. package/src/engine/graphics3/prefab/PrefabInstance.js +60 -80
  118. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts +16 -3
  119. package/src/engine/graphics3/prefab/prefab_instantiate.d.ts.map +1 -1
  120. package/src/engine/graphics3/prefab/prefab_instantiate.js +49 -12
  121. package/src/engine/graphics3/prefab/prefab_place.d.ts +31 -17
  122. package/src/engine/graphics3/prefab/prefab_place.d.ts.map +1 -1
  123. package/src/engine/graphics3/prefab/prefab_place.js +175 -130
  124. package/src/engine/graphics3/register_clips.d.ts +93 -0
  125. package/src/engine/graphics3/register_clips.d.ts.map +1 -0
  126. package/src/engine/graphics3/register_clips.js +166 -0
  127. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +9 -0
  128. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -1
  129. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +3 -1
  130. package/src/engine/physics/cloth/MEASUREMENTS.md +795 -0
  131. package/src/engine/physics/cloth/PLAN.md +349 -17
  132. package/src/engine/physics/cloth/build/ClothProxy.d.ts +113 -61
  133. package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -1
  134. package/src/engine/physics/cloth/build/ClothProxy.js +142 -77
  135. package/src/engine/physics/cloth/build/cloth_bind_in_frame.d.ts +44 -0
  136. package/src/engine/physics/cloth/build/cloth_bind_in_frame.d.ts.map +1 -0
  137. package/src/engine/physics/cloth/build/cloth_bind_in_frame.js +107 -0
  138. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +43 -30
  139. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -1
  140. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +371 -284
  141. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.d.ts +62 -0
  142. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.d.ts.map +1 -0
  143. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.js +275 -0
  144. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +7 -0
  145. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  146. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +85 -67
  147. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -1
  148. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +19 -101
  149. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +2 -5
  150. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -1
  151. package/src/engine/physics/cloth/collider/cloth_collider_pack.js +7 -15
  152. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +1 -1
  153. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -1
  154. package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +17 -62
  155. package/src/engine/physics/cloth/ecs/Cloth.d.ts +17 -9
  156. package/src/engine/physics/cloth/ecs/Cloth.d.ts.map +1 -1
  157. package/src/engine/physics/cloth/ecs/Cloth.js +220 -212
  158. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +0 -1
  159. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +6 -19
  160. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +1 -2
  161. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
  162. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +3 -12
  163. package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +6 -0
  164. package/src/engine/physics/cloth/ecs/ClothHold.d.ts +91 -0
  165. package/src/engine/physics/cloth/ecs/ClothHold.d.ts.map +1 -0
  166. package/src/engine/physics/cloth/ecs/ClothHold.js +103 -0
  167. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +254 -48
  168. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  169. package/src/engine/physics/cloth/ecs/ClothInstance.js +277 -47
  170. package/src/engine/physics/cloth/ecs/ClothPin.d.ts +73 -0
  171. package/src/engine/physics/cloth/ecs/ClothPin.d.ts.map +1 -0
  172. package/src/engine/physics/cloth/ecs/ClothPin.js +92 -0
  173. package/src/engine/physics/cloth/ecs/ClothPinSerializationAdapter.d.ts +36 -0
  174. package/src/engine/physics/cloth/ecs/ClothPinSerializationAdapter.d.ts.map +1 -0
  175. package/src/engine/physics/cloth/ecs/ClothPinSerializationAdapter.js +61 -0
  176. package/src/engine/physics/cloth/ecs/ClothRig.d.ts +23 -17
  177. package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -1
  178. package/src/engine/physics/cloth/ecs/ClothRig.js +29 -27
  179. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +12 -14
  180. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  181. package/src/engine/physics/cloth/ecs/ClothSystem.js +20 -15
  182. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +57 -36
  183. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  184. package/src/engine/physics/cloth/ecs/ClothWorld.js +754 -207
  185. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +12 -13
  186. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -1
  187. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +20 -14
  188. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +63 -11
  189. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  190. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +297 -101
  191. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +9 -7
  192. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  193. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +12 -10
  194. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +3 -3
  195. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  196. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +60 -120
  197. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts +37 -0
  198. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts.map +1 -0
  199. package/src/engine/physics/cloth/ecs/cloth_geodesic.js +135 -0
  200. package/src/engine/physics/cloth/ecs/cloth_holds.d.ts +97 -0
  201. package/src/engine/physics/cloth/ecs/cloth_holds.d.ts.map +1 -0
  202. package/src/engine/physics/cloth/ecs/cloth_holds.js +583 -0
  203. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +2 -2
  204. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -1
  205. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +168 -51
  206. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +17 -22
  207. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  208. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +55 -117
  209. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +21 -6
  210. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  211. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +51 -42
  212. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +5 -0
  213. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  214. package/src/engine/physics/cloth/ecs/cloth_write_back.js +62 -50
  215. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -1
  216. package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +65 -58
  217. package/src/engine/physics/cloth/playground/cloth_playground_build.d.ts.map +1 -1
  218. package/src/engine/physics/cloth/playground/cloth_playground_build.js +2 -3
  219. package/src/engine/physics/cloth/playground/collide_main.js +3 -29
  220. package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -1
  221. package/src/engine/physics/cloth/playground/garment_build.js +17 -5
  222. package/src/engine/physics/cloth/playground/garment_main.js +5 -20
  223. package/src/engine/physics/cloth/playground/main.js +6 -48
  224. package/src/engine/physics/cloth/playground/rig_build.d.ts.map +1 -1
  225. package/src/engine/physics/cloth/playground/rig_build.js +302 -296
  226. package/src/engine/physics/cloth/playground/rig_main.js +947 -955
  227. package/src/engine/physics/cloth/playground/wind_main.js +8 -7
  228. package/src/engine/physics/cloth/solver/ClothState.d.ts +45 -7
  229. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  230. package/src/engine/physics/cloth/solver/ClothState.js +79 -83
  231. package/src/engine/physics/cloth/solver/cloth_color_particles.d.ts +10 -8
  232. package/src/engine/physics/cloth/solver/cloth_color_particles.d.ts.map +1 -1
  233. package/src/engine/physics/cloth/solver/cloth_color_particles.js +30 -102
  234. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +6 -1
  235. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
  236. package/src/engine/physics/cloth/solver/cloth_contact_find.js +9 -3
  237. package/src/engine/physics/cloth/solver/cloth_hold_line.d.ts +114 -0
  238. package/src/engine/physics/cloth/solver/cloth_hold_line.d.ts.map +1 -0
  239. package/src/engine/physics/cloth/solver/cloth_hold_line.js +350 -0
  240. package/src/engine/physics/cloth/solver/cloth_kinetic_energy.d.ts +21 -0
  241. package/src/engine/physics/cloth/solver/cloth_kinetic_energy.d.ts.map +1 -0
  242. package/src/engine/physics/cloth/solver/cloth_kinetic_energy.js +45 -0
  243. package/src/engine/physics/cloth/solver/cloth_particle_coupling.d.ts +32 -0
  244. package/src/engine/physics/cloth/solver/cloth_particle_coupling.d.ts.map +1 -0
  245. package/src/engine/physics/cloth/solver/cloth_particle_coupling.js +133 -0
  246. package/src/engine/physics/cloth/solver/cloth_particle_of_source.d.ts +16 -0
  247. package/src/engine/physics/cloth/solver/cloth_particle_of_source.d.ts.map +1 -0
  248. package/src/engine/physics/cloth/solver/cloth_particle_of_source.js +19 -0
  249. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +6 -2
  250. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -1
  251. package/src/engine/physics/cloth/solver/cloth_self_find.js +19 -68
  252. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
  253. package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +30 -65
  254. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +24 -12
  255. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  256. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +24 -12
  257. package/src/engine/physics/cloth/solver/cloth_step.d.ts +16 -0
  258. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  259. package/src/engine/physics/cloth/solver/cloth_step.js +189 -48
  260. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +1 -0
  261. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -1
  262. package/src/engine/physics/cloth/solver/cloth_step_instance.js +12 -6
  263. package/src/engine/physics/cloth/solver/cloth_stretch_residual.d.ts +21 -0
  264. package/src/engine/physics/cloth/solver/cloth_stretch_residual.d.ts.map +1 -0
  265. package/src/engine/physics/cloth/solver/cloth_stretch_residual.js +54 -0
  266. package/src/engine/physics/cloth/solver/cloth_vertex_accumulator.d.ts +15 -0
  267. package/src/engine/physics/cloth/solver/cloth_vertex_accumulator.d.ts.map +1 -1
  268. package/src/engine/physics/cloth/solver/cloth_vertex_accumulator.js +28 -0
  269. package/src/engine/physics/cloth/solver/cloth_worst_tether_ratio.d.ts +30 -0
  270. package/src/engine/physics/cloth/solver/cloth_worst_tether_ratio.d.ts.map +1 -0
  271. package/src/engine/physics/cloth/solver/cloth_worst_tether_ratio.js +56 -0
  272. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -1
  273. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +3 -17
  274. package/src/engine/physics/cloth/solver/constraint/cloth_leash_row.d.ts +1 -1
  275. package/src/engine/physics/cloth/solver/constraint/cloth_leash_row.d.ts.map +1 -1
  276. package/src/engine/physics/cloth/solver/constraint/cloth_leash_row.js +353 -346
  277. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +7 -5
  278. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -1
  279. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +7 -5
  280. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +2 -1
  281. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -1
  282. package/src/engine/physics/cloth/wind/ClothFluidWind.js +7 -25
  283. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +12 -10
  284. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -1
  285. package/src/engine/physics/cloth/wind/cloth_sample_air.js +60 -82
  286. package/src/engine/physics/ecs/ContactProbeSystem.d.ts.map +1 -1
  287. package/src/engine/physics/ecs/ContactProbeSystem.js +2 -18
  288. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  289. package/src/engine/physics/ecs/PhysicsSystem.js +9 -50
  290. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  291. package/src/engine/physics/fluid/ecs/fluid_sample_domain_bounds.d.ts +24 -0
  292. package/src/engine/physics/fluid/ecs/fluid_sample_domain_bounds.d.ts.map +1 -0
  293. package/src/engine/physics/fluid/ecs/fluid_sample_domain_bounds.js +42 -0
  294. package/src/engine/physics/fluid/ecs/fluid_sample_velocity_world_mps.d.ts +25 -0
  295. package/src/engine/physics/fluid/ecs/fluid_sample_velocity_world_mps.d.ts.map +1 -0
  296. package/src/engine/physics/fluid/ecs/fluid_sample_velocity_world_mps.js +40 -0
  297. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.d.ts.map +1 -1
  298. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.js +40 -90
  299. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  300. package/src/engine/physics/narrowphase/capsule_contacts.js +3 -5
  301. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -1
  302. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +2 -14
  303. package/src/engine/physics/narrowphase/hull_sphere_contact.d.ts.map +1 -1
  304. package/src/engine/physics/narrowphase/hull_sphere_contact.js +2 -5
  305. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  306. package/src/engine/physics/narrowphase/narrowphase_step.js +13 -32
  307. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  308. package/src/engine/physics/narrowphase/sphere_box_contact.js +2 -5
  309. package/src/engine/simulation/Ticker.d.ts +13 -0
  310. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  311. package/src/engine/simulation/Ticker.js +43 -8
  312. package/src/format/texture/basis/transcode/etc1s_to_astc.d.ts.map +1 -1
  313. package/src/format/texture/basis/transcode/etc1s_to_astc.js +2 -1
  314. package/src/shade/RENDERER_CONTRACT.md +8 -2
  315. package/src/shade/playground/image_decode_bench/main.js +2 -1
  316. package/src/shade/playground/mesh_row_ceiling/main.js +2 -1
  317. package/src/shade/playground/skinned_blas_refit/diff_traced_against_rasterized.d.ts.map +1 -1
  318. package/src/shade/playground/skinned_blas_refit/diff_traced_against_rasterized.js +2 -1
  319. package/src/shade/playground/skinned_blas_refit/main.d.ts.map +1 -1
  320. package/src/shade/playground/skinned_blas_refit/main.js +11 -19
  321. package/src/shade/playground/skinned_blas_refit/place_character.d.ts +6 -3
  322. package/src/shade/playground/skinned_blas_refit/place_character.d.ts.map +1 -1
  323. package/src/shade/playground/skinned_blas_refit/place_character.js +90 -90
  324. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -1
  325. package/src/shade/playground/skinned_mesh_soup/main.js +502 -509
  326. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -1
  327. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +2 -1
  328. package/src/shade/playground/ssr_variance/statistics.d.ts.map +1 -1
  329. package/src/shade/playground/ssr_variance/statistics.js +2 -1
  330. package/src/shade/playground/texture_encode/README.md +129 -109
  331. package/src/shade/playground/texture_encode/main.d.ts.map +1 -1
  332. package/src/shade/playground/texture_encode/main.js +1431 -1430
  333. package/src/shade/playground/volumetrics_froxel/measure.d.ts.map +1 -1
  334. package/src/shade/playground/volumetrics_froxel/measure.js +2 -1
  335. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +45 -14
  336. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  337. package/src/shade/renderer/animation/GPUAnimationManager.js +316 -64
  338. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts +5 -0
  339. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  340. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +11 -1
  341. package/src/shade/renderer/buffer/table/gpu_database_check_links.d.ts.map +1 -1
  342. package/src/shade/renderer/buffer/table/gpu_database_check_links.js +2 -1
  343. package/src/shade/renderer/geometry/BoxGeometry.d.ts +1 -11
  344. package/src/shade/renderer/geometry/BoxGeometry.d.ts.map +1 -1
  345. package/src/shade/renderer/geometry/BoxGeometry.js +6 -13
  346. package/src/shade/renderer/geometry/CapsuleGeometry.d.ts +19 -0
  347. package/src/shade/renderer/geometry/CapsuleGeometry.d.ts.map +1 -0
  348. package/src/shade/renderer/geometry/CapsuleGeometry.js +24 -0
  349. package/src/shade/renderer/geometry/ConeGeometry.d.ts +20 -0
  350. package/src/shade/renderer/geometry/ConeGeometry.d.ts.map +1 -0
  351. package/src/shade/renderer/geometry/ConeGeometry.js +33 -0
  352. package/src/shade/renderer/geometry/CylinderGeometry.d.ts +21 -0
  353. package/src/shade/renderer/geometry/CylinderGeometry.d.ts.map +1 -0
  354. package/src/shade/renderer/geometry/CylinderGeometry.js +35 -0
  355. package/src/shade/renderer/geometry/PlaneGeometry.d.ts +17 -0
  356. package/src/shade/renderer/geometry/PlaneGeometry.d.ts.map +1 -0
  357. package/src/shade/renderer/geometry/PlaneGeometry.js +22 -0
  358. package/src/shade/renderer/geometry/TorusGeometry.d.ts +18 -0
  359. package/src/shade/renderer/geometry/TorusGeometry.d.ts.map +1 -0
  360. package/src/shade/renderer/geometry/TorusGeometry.js +23 -0
  361. package/src/shade/renderer/geometry/UVSphereGeometry.d.ts +21 -0
  362. package/src/shade/renderer/geometry/UVSphereGeometry.d.ts.map +1 -0
  363. package/src/shade/renderer/geometry/UVSphereGeometry.js +34 -0
  364. package/src/shade/renderer/geometry/primitives/make_capsule_geometry.d.ts +19 -0
  365. package/src/shade/renderer/geometry/primitives/make_capsule_geometry.d.ts.map +1 -0
  366. package/src/shade/renderer/geometry/primitives/make_capsule_geometry.js +205 -0
  367. package/src/shade/renderer/geometry/primitives/make_cone_geometry.d.ts +21 -0
  368. package/src/shade/renderer/geometry/primitives/make_cone_geometry.d.ts.map +1 -0
  369. package/src/shade/renderer/geometry/primitives/make_cone_geometry.js +36 -0
  370. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.d.ts +1 -1
  371. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.d.ts.map +1 -1
  372. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +44 -9
  373. package/src/shade/renderer/geometry/primitives/make_uv_sphere_geometry.d.ts +20 -0
  374. package/src/shade/renderer/geometry/primitives/make_uv_sphere_geometry.d.ts.map +1 -0
  375. package/src/shade/renderer/geometry/primitives/make_uv_sphere_geometry.js +190 -0
  376. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.d.ts.map +1 -1
  377. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +3 -2
  378. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.d.ts.map +1 -1
  379. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.js +2 -1
  380. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.d.ts.map +1 -1
  381. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.js +2 -1
  382. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.d.ts.map +1 -1
  383. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.js +2 -1
  384. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.d.ts.map +1 -1
  385. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.js +3 -2
  386. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.d.ts.map +1 -1
  387. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.js +2 -1
  388. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts.map +1 -1
  389. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.js +2 -1
  390. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  391. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +3 -2
  392. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
  393. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +7 -2
  394. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.d.ts +26 -8
  395. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.d.ts.map +1 -1
  396. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.js +56 -39
  397. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.d.ts +7 -0
  398. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.d.ts.map +1 -1
  399. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/shader_prefix_scan_csdldf.js +46 -4
  400. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/test_prefix_sum.d.ts.map +1 -1
  401. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/test_prefix_sum.js +4 -2
  402. package/src/shade/renderer/gpu_primitive/sort/defs.d.ts +2 -2
  403. package/src/shade/renderer/gpu_primitive/sort/defs.js +79 -79
  404. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts +1 -1
  405. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts.map +1 -1
  406. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.js +7 -3
  407. package/src/shade/renderer/particles/GPUParticleSystem.js +1 -1
  408. package/src/shade/renderer/particles/coherence/graph_particle_bucket.d.ts +4 -3
  409. package/src/shade/renderer/particles/coherence/graph_particle_bucket.d.ts.map +1 -1
  410. package/src/shade/renderer/particles/coherence/graph_particle_bucket.js +200 -194
  411. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  412. package/src/shade/renderer/particles/graph_particles.js +6 -2
  413. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -1
  414. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +3 -2
  415. package/src/shade/renderer/particles/warmup/graph_particle_warmup.d.ts.map +1 -1
  416. package/src/shade/renderer/particles/warmup/graph_particle_warmup.js +334 -329
  417. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  418. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +7 -2
  419. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
  420. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +16 -4
  421. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +2444 -2345
  422. package/src/shade/renderer/texture/encode/chunk_astc_encode_block_4x4_rgb.d.ts.map +1 -1
  423. package/src/shade/renderer/texture/encode/chunk_astc_encode_block_4x4_rgb.js +312 -310
  424. package/src/shade/renderer/texture/encode/chunk_bc1_encode_block.d.ts +3 -3
  425. package/src/shade/renderer/texture/encode/chunk_bc1_encode_block.d.ts.map +1 -1
  426. package/src/shade/renderer/texture/encode/chunk_bc1_encode_block.js +383 -375
  427. package/src/shade/renderer/texture/encode/chunk_bc6h_encode_block_mode11.d.ts.map +1 -1
  428. package/src/shade/renderer/texture/encode/chunk_bc6h_encode_block_mode11.js +349 -346
  429. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block.d.ts +58 -0
  430. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block.d.ts.map +1 -0
  431. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block.js +536 -0
  432. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block_mode6.d.ts +21 -7
  433. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block_mode6.d.ts.map +1 -1
  434. package/src/shade/renderer/texture/encode/chunk_bc7_encode_block_mode6.js +291 -327
  435. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode1.d.ts +25 -0
  436. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode1.d.ts.map +1 -0
  437. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode1.js +99 -0
  438. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode6.d.ts +5 -3
  439. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode6.d.ts.map +1 -1
  440. package/src/shade/renderer/texture/encode/chunk_bc7_pack_mode6.js +47 -71
  441. package/src/shade/renderer/texture/encode/chunk_bc7_partition_2.d.ts +10 -0
  442. package/src/shade/renderer/texture/encode/chunk_bc7_partition_2.d.ts.map +1 -0
  443. package/src/shade/renderer/texture/encode/chunk_bc7_partition_2.js +50 -0
  444. package/src/shade/renderer/texture/encode/shader_texture_encode_bc7.d.ts +4 -5
  445. package/src/shade/renderer/texture/encode/shader_texture_encode_bc7.d.ts.map +1 -1
  446. package/src/shade/renderer/texture/encode/shader_texture_encode_bc7.js +7 -8
  447. package/src/shade/renderer/water/chunk_water_transport.d.ts.map +1 -1
  448. package/src/shade/renderer/water/chunk_water_transport.js +7 -5
  449. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +1 -1
  450. package/src/shade/renderer/water/medium/GPUWaterBodies.js +8 -2
  451. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.d.ts +3 -2
  452. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.d.ts.map +1 -1
  453. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.js +3 -2
  454. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  455. package/src/shade/renderer/water/shader_water.js +3 -1
  456. package/src/shade/renderer/water/surface/GPUWaterSurface.d.ts.map +1 -1
  457. package/src/shade/renderer/water/surface/GPUWaterSurface.js +3 -1
  458. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts.map +1 -1
  459. package/src/shade/renderer/water/surface/graph_water_tessellation.js +11 -4
  460. package/src/shade/renderer/water/surface/particles/GPUWaterParticles.d.ts.map +1 -1
  461. package/src/shade/renderer/water/surface/particles/GPUWaterParticles.js +37 -20
  462. package/src/shade/wgsl/validate_wgsl_source.d.ts.map +1 -1
  463. package/src/shade/wgsl/validate_wgsl_source.js +74 -3
  464. package/src/engine/graphics3/register_instance_animation.d.ts +0 -114
  465. package/src/engine/graphics3/register_instance_animation.d.ts.map +0 -1
  466. package/src/engine/graphics3/register_instance_animation.js +0 -201
@@ -1,509 +1,502 @@
1
- import Vector3 from "../../../core/geom/Vector3.js";
2
- import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
3
- import ConcurrentExecutor from "../../../core/process/executor/ConcurrentExecutor.js";
4
- import { AssetManager } from "../../../engine/asset/AssetManager.js";
5
- import { GameAssetType } from "../../../engine/asset/GameAssetType.js";
6
- import { ArrayBufferLoader } from "../../../engine/asset/loaders/ArrayBufferLoader.js";
7
- import {
8
- GLTFSceneBundleAssetLoader
9
- } from "../../../engine/asset/loaders/GLTFSceneBundleAssetLoader.js";
10
- import { ImageBitmapAssetLoader } from "../../../engine/asset/loaders/image/ImageBitmapAssetLoader.js";
11
- import { load_model_scene_bundle } from "../../../engine/asset/load_model_scene_bundle.js";
12
- import { EntityComponentDataset } from "../../../engine/ecs/EntityComponentDataset.js";
13
- import { prefab_compile } from "../../../engine/graphics3/prefab/prefab_compile.js";
14
- import { AnimationClipFlags } from "../../renderer/animation/AnimationClipFlags.js";
15
- import { camera_frame_sphere } from "../../renderer/camera/camera_frame_sphere.js";
16
- import { OrbitalCameraController } from "../../renderer/camera/OrbitalCameraController.js";
17
- import { PerspectiveCamera } from "../../renderer/camera/PerspectiveCamera.js";
18
- import { create_frame_loop } from "../../renderer/create_frame_loop.js";
19
- import { make_sunlight } from "../../renderer/light/make_sunlight.js";
20
- import { place_light } from "../../renderer/light/place_light.js";
21
- import { Renderer } from "../../renderer/Renderer.js";
22
- import { row_of_entity } from "../../renderer/scene/rows/GPUSceneRows.js";
23
- import { Scene } from "../../renderer/scene/Scene.js";
24
- import { load_default_environment } from "../load_default_environment.js";
25
- import { grid_position, place_character } from "../skinned_blas_refit/place_character.js";
26
-
27
- /**
28
- * A skinned character, loaded the way the game loads one, with the pieces that could be making soup
29
- * put on switches.
30
- *
31
- * ## Why this page exists next to `skinned_blas_refit`
32
- *
33
- * That page renders the same character and answers a sharp question about the BLAS — does the tree
34
- * follow the mesh — with `verify()`. It answers that question *numerically*, by holding the tree's
35
- * root box against the bounds chain's box over the same deformed vertices, and both of those are
36
- * derived from the clone's vertex region. **Vertices that are wrong in the same way agree with each
37
- * other perfectly.** So a refit can be correct, `verify()` green, every internal node exactly its
38
- * children's union — and the picture still soup, because the thing the tree faithfully bounds is
39
- * itself wrong.
40
- *
41
- * This page is that other question, and it is built to bisect it rather than to look good:
42
- *
43
- * - **`?skin=off`** — no skin registered. A skinned mesh entity still gets a vertex clone (see
44
- * {@link GPUMeshSkinningContext#obtain_clone}), but nothing ever deforms it, so what is drawn is
45
- * the memcpy of the source: the rest pose, under the mesh entity's row — which the prefab put
46
- * into the skin's own frame. Soup here is the geometry upload — the meshlet copy or its address
47
- * patch — and has nothing to do with skinning.
48
- * - **`?skin=bind`** — the skin registered, no clip. The skinning pass runs every frame over a pose
49
- * that never changes, and the answer it should write is one the CPU knows exactly. `diagnose()`
50
- * in the console reads the clone's vertices back and says which of them are wrong and in what
51
- * pattern — see {@link verify_clone_vertices}. Soup here is the skinning machinery.
52
- * - **`?skin=animated`** — the dance, as `skinned_blas_refit` runs it. Soup only here is the pose:
53
- * the joint matrices, their range, or the order the skin's joints were resolved in.
54
- *
55
- * ## What the bisect found, and what was done
56
- *
57
- * `chunk_skin_blend_mesh_local` folds `inverse(mesh.transform_global)` into each joint matrix and
58
- * converts the product to a dual quaternion, which can only carry a rigid motion. Michelle's
59
- * positions and inverse-bind matrices are authored in the already-scaled space, so
60
- * `joint.global × inverse_bind` is the *identity* and the fold was a bare 100×.
61
- * `dual_quat_from_m4` reads `sqrt(max(0, 1 ± m00 ± m11 ± m22))` over terms of `1 + 100·(…)`, clamps
62
- * whichever go negative and normalizes the rest, so the rotation it extracted was wrong — and wrong
63
- * differently per joint, which is what tore the mesh apart. `?skin=bind` was the right character at
64
- * 1/100 size (one shared matrix, one shared error) and `?skin=animated` was soup.
65
- *
66
- * The frame is now the engine's rather than the asset's: compiling a model puts each skinned mesh
67
- * on its skin's own bind transform, so the fold is the identity at bind and a rigid displacement
68
- * under a clip, for every shape a glTF file arrives in. See
69
- * `renderer/animation/SKINNING_FRAME_PLAN.md`.
70
- *
71
- * All of which was established without a device — `__soup_blend_probe.spec.js` runs the shader's own
72
- * blend over this character's own matrices, and the emulator tier in
73
- * `chunk_skin_blend_mesh_local.spec.js` runs the real WGSL over both shapes. This page is where that
74
- * is confirmed against pixels, which by then is a formality.
75
- *
76
- * ## Why through the asset framework
77
- *
78
- * `skinned_blas_refit` calls `load_gltf` directly and parses the file once per character.
79
- * {@link GLTFSceneBundleAssetLoader} is what the game and the editor register, and it hands **the
80
- * same bundle** to every caller — so a second character is not a second parse, it is
81
- * `prefab_instantiate` over the prefab compiled from it once: its own entities, its own skins bound
82
- * to those entities' joints, its own clips retargeted onto them, sharing the geometry and the
83
- * inverse-bind matrices. That is a
84
- * materially different path into the renderer, and running the repro on it means a fix found here
85
- * is a fix for the engine rather than for one prototype page.
86
- *
87
- * Runs under the dev server (`npm run dev`), which serves `test_assets/`.
88
- *
89
- * @author Alex Goldring
90
- * @copyright Company Named Limited (c) 2026
91
- */
92
-
93
- /**
94
- * Served by the dev server out of `test_assets/`, which is gitignored and unpublished. See
95
- * `test_assets/README.md`.
96
- *
97
- * @type {string}
98
- */
99
- const CHARACTER_URL = '/test_assets/Michelle.glb';
100
-
101
- /**
102
- * How far apart characters stand, in character heights.
103
- *
104
- * @type {number}
105
- */
106
- const SPACING = 1.1;
107
-
108
- /**
109
- * How the skin is driven. The page's whole point is that these are three different questions.
110
- *
111
- * @enum {string}
112
- */
113
- const SkinMode = {
114
- /** no skin registered — the clone is never deformed */
115
- Off: 'off',
116
- /** skin registered, no clip — the pose the CPU can predict */
117
- Bind: 'bind',
118
- /** skin registered, the file's dance bound and playing */
119
- Animated: 'animated',
120
- };
121
-
122
- /**
123
- * The manager a model load needs, registered the way the game's graphics profile registers it.
124
- *
125
- * Not {@link make_playground_asset_manager}: that one is the two-loader subset `load_gltf` needs
126
- * when a page calls it by hand. Going through {@link load_model_scene_bundle} means the *model* is
127
- * an asset too, which is the thing this page wants to be on.
128
- *
129
- * @returns {Promise<AssetManager>}
130
- */
131
- async function make_asset_manager() {
132
- const manager = new AssetManager({
133
- context: {},
134
- executor: new ConcurrentExecutor(),
135
- });
136
-
137
- // One instance for both glTF types, as `addModelLoaders` does — the loader holds no per-type
138
- // state, and the two types are `.glb` and `.gltf` telling themselves apart.
139
- const models = new GLTFSceneBundleAssetLoader();
140
-
141
- await manager.registerLoader(GameAssetType.ModelGLTF, models);
142
- await manager.registerLoader(GameAssetType.ModelGLTF_JSON, models);
143
-
144
- // the container's `.bin` buffers, and its images — `load_gltf` asks for both back through the
145
- // manager rather than fetching them itself
146
- await manager.registerLoader(GameAssetType.ArrayBuffer, new ArrayBufferLoader());
147
- await manager.registerLoader(GameAssetType.ImageBitmap, new ImageBitmapAssetLoader());
148
-
149
- manager.startup();
150
-
151
- return manager;
152
- }
153
-
154
- /**
155
- * Every skinned mesh entity an instance's skins deform.
156
- *
157
- * Off the skins rather than by walking the entities: a skin already lists exactly the meshes that
158
- * bind to it, which is what `register_skin` uses.
159
- *
160
- * @param {PrefabInstance} instance
161
- * @returns {number[]}
162
- */
163
- function skinned_meshes_of(instance) {
164
- const meshes = new Set();
165
-
166
- for (const skin of instance.skins) {
167
- for (const mesh of skin.meshes) {
168
- meshes.add(mesh);
169
- }
170
- }
171
-
172
- return [...meshes];
173
- }
174
-
175
- /**
176
- * The world-space box the renderer culls and traces this mesh by — the `meshes` table row, which
177
- * the skinning pass's bounds-refresh chain rewrites every frame from the vertices it just moved.
178
- *
179
- * Not the source geometry's box under the mesh entity's transform: that is the rest pose, and
180
- * the dance has moved on from it.
181
- *
182
- * @param {GPUSceneContext} scene_context
183
- * @param {number} entity a mesh entity
184
- * @returns {Promise<{low: Vector3, high: Vector3}>}
185
- */
186
- async function world_bounds_of(scene_context, entity) {
187
- const row = row_of_entity(entity);
188
-
189
- const record = await scene_context.scene_database.debug_table_read_element('meshes', row);
190
-
191
- const box = record.bounding_box;
192
-
193
- return {
194
- low: new Vector3(box[0], box[1], box[2]),
195
- high: new Vector3(box[3], box[4], box[5]),
196
- };
197
- }
198
-
199
- /**
200
- * Stand the page up.
201
- *
202
- * @returns {Promise<void>}
203
- */
204
- export async function start() {
205
-
206
- const element = id => document.getElementById(id);
207
-
208
- const status_element = element('status');
209
- const stats_element = element('stats');
210
-
211
- /**
212
- * @param {string} text
213
- * @param {string} [kind]
214
- */
215
- function set_status(text, kind = '') {
216
- status_element.textContent = text;
217
- status_element.className = kind;
218
- }
219
-
220
- const canvas = element('view');
221
-
222
- const context = canvas.getContext('webgpu');
223
-
224
- if (context === null) {
225
- set_status(
226
- 'no WebGPU context — this browser has none, or the page is not a secure context',
227
- 'fail'
228
- );
229
- return;
230
- }
231
-
232
- // ---- what the url asks for ------------------------------------------------------------------
233
-
234
- const parameters = new URLSearchParams(location.search);
235
-
236
- const count = Math.min(64, Math.max(1, Number.parseInt(parameters.get('count'), 10) || 1));
237
-
238
- const skin_mode = Object.values(SkinMode).includes(parameters.get('skin'))
239
- ? parameters.get('skin')
240
- : SkinMode.Animated;
241
-
242
- const tracing = parameters.get('trace') === '1';
243
-
244
- element('character_count').value = String(count);
245
- element('skin_mode').value = skin_mode;
246
- element('path_tracing').checked = tracing;
247
-
248
- // A Scene has no teardown, so changing any of these reloads the page rather than emptying one
249
- // in place. Every switch is in the url for the same reason a repro is a url: it can be sent.
250
- function reload_with(changes) {
251
- const next = new URLSearchParams(location.search);
252
-
253
- for (const [key, value] of Object.entries(changes)) {
254
- next.set(key, value);
255
- }
256
-
257
- location.search = next.toString();
258
- }
259
-
260
- element('respawn').addEventListener('click', () => reload_with({
261
- count: element('character_count').value,
262
- skin: element('skin_mode').value,
263
- }));
264
-
265
- element('skin_mode').addEventListener('change', () => reload_with({
266
- skin: element('skin_mode').value,
267
- }));
268
-
269
- // ---- the renderer ---------------------------------------------------------------------------
270
-
271
- const renderer = new Renderer();
272
-
273
- set_status('acquiring a device…');
274
-
275
- await renderer.initialize({ context });
276
-
277
- if (!renderer.graphics.device.features.has('subgroups')) {
278
- set_status('device has no `subgroups`, which this engine takes as a baseline', 'warn');
279
- }
280
-
281
- const camera = new PerspectiveCamera();
282
-
283
- camera.near = 0.01;
284
- camera.far = 200;
285
- camera.fov = 50 * DEG_TO_RAD;
286
- camera.aspect = renderer.aspect_ratio;
287
-
288
- const scene = new Scene();
289
-
290
- // the characters are entities, and the scene's rows are theirs
291
- const dataset = new EntityComponentDataset();
292
-
293
- scene.dataset = dataset;
294
-
295
- // document.body rather than the canvas: the controller starts a keyboard device, and only a
296
- // focusable element receives key events — body carries tabindex for exactly that.
297
- const controller = new OrbitalCameraController(camera, document.body);
298
-
299
- // Rasterized by default. It is the simpler of the two readings — it reads the deformed vertices
300
- // directly, with no tree in the way — so it is the one to establish the answer on first.
301
- renderer.feature_path_tracing_enabled = tracing;
302
- renderer.feature_taa_enabled = false;
303
-
304
- element('path_tracing').addEventListener('change', event => {
305
- renderer.feature_path_tracing_enabled = event.target.checked;
306
- renderer.indicate_view_change();
307
- });
308
-
309
- globalThis.renderer = renderer;
310
- globalThis.scene = scene;
311
- globalThis.camera = camera;
312
- globalThis.controller = controller;
313
-
314
- // ---- the cast -------------------------------------------------------------------------------
315
-
316
- set_status(`loading ${CHARACTER_URL} …`);
317
-
318
- const scene_context = renderer.scenes.obtain(scene);
319
-
320
- /** @type {number[]} the skinned mesh entities */
321
- const characters = [];
322
-
323
- try {
324
- const asset_manager = await make_asset_manager();
325
-
326
- // One load and one compile, whatever the count: the asset hands out the model itself, and
327
- // a placement is `prefab_instantiate` over the prefab compiled from it.
328
- const bundle = await load_model_scene_bundle(asset_manager, CHARACTER_URL);
329
-
330
- const prefab = prefab_compile(bundle);
331
-
332
- const animations = scene_context.animation_manager;
333
-
334
- for (let i = 0; i < count; i++) {
335
- const { x, z } = grid_position(i, count, SPACING);
336
-
337
- const { instance } = place_character(scene_context, dataset, prefab, x, z, {
338
- register_animation: skin_mode !== SkinMode.Off,
339
- });
340
-
341
- const meshes = skinned_meshes_of(instance);
342
-
343
- if (meshes.length === 0) {
344
- throw new Error(`${CHARACTER_URL} has no skinned mesh`);
345
- }
346
-
347
- if (skin_mode === SkinMode.Animated) {
348
- // One clip, looping, at full weight. The pose accumulator sums every *bound* clip
349
- // whatever its weight, so binding the T-pose alongside the dance would blend the
350
- // two — only what should play is registered.
351
- const clip = instance.clips.find(candidate => /samba|dance/i.test(candidate.name ?? ''))
352
- ?? instance.clips[0];
353
-
354
- if (clip !== undefined) {
355
- const clip_id = animations.register_clip(clip);
356
-
357
- animations.set_playback_weight(clip_id, 1);
358
- animations.set_flags(clip_id, AnimationClipFlags.Loop);
359
- animations.start(clip_id);
360
- }
361
- }
362
-
363
- characters.push(...meshes);
364
- }
365
- } catch (error) {
366
- set_status(
367
- `could not load the character (${error.message}). Put it under test_assets/ — `
368
- + `see test_assets/README.md.`,
369
- 'fail'
370
- );
371
- console.error(error);
372
- return;
373
- }
374
-
375
- if (!scene.lights.elements.some(light => light.isDirectionalLight)) {
376
- // a glTF character carries no light of its own
377
- place_light(scene, make_sunlight({ intensity: 6 }));
378
- }
379
-
380
- // ---- frames enough for the box to be worth framing on ----------------------------------------
381
-
382
- set_status('first frames — uploading clones and their trees …');
383
-
384
- for (let i = 0; i < 8; i++) {
385
- renderer.render(camera, scene, 1 / 60);
386
-
387
- await renderer.graphics.device.queue.onSubmittedWorkDone();
388
- }
389
-
390
- let low = null;
391
- let high = null;
392
-
393
- for (const character of characters) {
394
- const bounds = await world_bounds_of(scene_context, character);
395
-
396
- low = low === null ? bounds.low : new Vector3(
397
- Math.min(low.x, bounds.low.x), Math.min(low.y, bounds.low.y), Math.min(low.z, bounds.low.z)
398
- );
399
-
400
- high = high === null ? bounds.high : new Vector3(
401
- Math.max(high.x, bounds.high.x), Math.max(high.y, bounds.high.y), Math.max(high.z, bounds.high.z)
402
- );
403
- }
404
-
405
- const span = new Vector3(high.x - low.x, high.y - low.y, high.z - low.z);
406
-
407
- const radius = Math.hypot(span.x, span.y, span.z) * 0.5;
408
-
409
- camera_frame_sphere(
410
- controller,
411
- radius,
412
- new Vector3((low.x + high.x) / 2, (low.y + high.y) / 2, (low.z + high.z) / 2)
413
- );
414
-
415
- controller.movement_speed_scale = radius * 0.2;
416
-
417
- camera.near = radius * 0.01;
418
- camera.far = radius * 400;
419
-
420
- console.log(
421
- `published box: low=(${low.x.toFixed(4)}, ${low.y.toFixed(4)}, ${low.z.toFixed(4)}) `
422
- + `high=(${high.x.toFixed(4)}, ${high.y.toFixed(4)}, ${high.z.toFixed(4)}) — `
423
- + `span ${span.x.toFixed(4)} × ${span.y.toFixed(4)} × ${span.z.toFixed(4)} m`
424
- );
425
-
426
- load_default_environment(scene).then(applied => {
427
- if (applied) {
428
- renderer.indicate_view_change();
429
- }
430
- });
431
-
432
- // the controller's keyboard device listens on body, and only a focused element gets key events
433
- document.body.focus();
434
-
435
- globalThis.characters = characters;
436
-
437
- // ---- the console half ------------------------------------------------------------------------
438
-
439
- let animating = skin_mode === SkinMode.Animated;
440
-
441
- /**
442
- * The clone's deformed vertices, against the answer the CPU knows for the bind pose.
443
- *
444
- * Only meaningful under `?skin=bind`: with a clip playing there is no CPU-side answer to compare
445
- * against, and with no skin registered there is nothing deforming the clone. It runs in the
446
- * other two modes anyway rather than refusing — under `?skin=off` a pass says the clone's copy
447
- * and address patch are right, which is worth knowing on its own.
448
- */
449
- globalThis.diagnose = async () => {
450
- const { verify_clone_vertices } = await import('./verify_clone_vertices.js');
451
-
452
- const was_animating = animating;
453
-
454
- animating = false;
455
-
456
- try {
457
- return await verify_clone_vertices({ renderer, scene, entity: characters[0] });
458
- } finally {
459
- animating = was_animating;
460
- }
461
- };
462
-
463
- set_status(
464
- `loaded — ${count} × ${CHARACTER_URL}, skin=${skin_mode}. `
465
- + `\`await diagnose()\` in the console.`,
466
- 'pass'
467
- );
468
-
469
- renderer.indicate_view_change();
470
-
471
- // ---- frame loop --------------------------------------------------------------------------
472
-
473
- element('animation').disabled = skin_mode !== SkinMode.Animated;
474
-
475
- if (skin_mode !== SkinMode.Animated) {
476
- // there is no clip to freeze; saying so beats a live-looking button that does nothing
477
- element('animation').textContent = 'No clip playing';
478
- }
479
-
480
- element('animation').addEventListener('click', () => {
481
- animating = !animating;
482
-
483
- element('animation').textContent = animating ? 'Freeze pose' : 'Resume dance';
484
-
485
- renderer.path_tracer.clear_history = true;
486
- renderer.indicate_view_change();
487
- });
488
-
489
- const FRAME_RATE_LIMIT = 60;
490
-
491
- create_frame_loop(time_delta_s => {
492
- const width = window.innerWidth;
493
- const height = window.innerHeight;
494
-
495
- renderer.resize(width, height);
496
- renderer.pixel_ratio = window.devicePixelRatio;
497
- camera.aspect = width / height;
498
-
499
- controller.update();
500
-
501
- // A frozen pose is a still scene, which is the only condition under which the tracer's
502
- // accumulator converges — so time stops rather than the frame loop stopping.
503
- renderer.render(camera, scene, animating ? time_delta_s : 0);
504
-
505
- stats_element.textContent =
506
- `${count} × skin=${skin_mode}`
507
- + `${renderer.feature_path_tracing_enabled ? ' · path traced' : ' · rasterized'}`;
508
- }, FRAME_RATE_LIMIT);
509
- }
1
+ import Vector3 from "../../../core/geom/Vector3.js";
2
+ import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
3
+ import ConcurrentExecutor from "../../../core/process/executor/ConcurrentExecutor.js";
4
+ import { AssetManager } from "../../../engine/asset/AssetManager.js";
5
+ import { GameAssetType } from "../../../engine/asset/GameAssetType.js";
6
+ import { ArrayBufferLoader } from "../../../engine/asset/loaders/ArrayBufferLoader.js";
7
+ import {
8
+ GLTFSceneBundleAssetLoader
9
+ } from "../../../engine/asset/loaders/GLTFSceneBundleAssetLoader.js";
10
+ import { ImageBitmapAssetLoader } from "../../../engine/asset/loaders/image/ImageBitmapAssetLoader.js";
11
+ import { load_model_scene_bundle } from "../../../engine/asset/load_model_scene_bundle.js";
12
+ import { EntityComponentDataset } from "../../../engine/ecs/EntityComponentDataset.js";
13
+ import { MeshSkin } from "../../../engine/graphics/ecs/mesh-v2/MeshSkin.js";
14
+ import { AnimationClips } from "../../../engine/graphics3/animation/AnimationClips.js";
15
+ import { prefab_compile } from "../../../engine/graphics3/prefab/prefab_compile.js";
16
+ import { AnimationClipFlags } from "../../renderer/animation/AnimationClipFlags.js";
17
+ import { camera_frame_sphere } from "../../renderer/camera/camera_frame_sphere.js";
18
+ import { OrbitalCameraController } from "../../renderer/camera/OrbitalCameraController.js";
19
+ import { PerspectiveCamera } from "../../renderer/camera/PerspectiveCamera.js";
20
+ import { create_frame_loop } from "../../renderer/create_frame_loop.js";
21
+ import { make_sunlight } from "../../renderer/light/make_sunlight.js";
22
+ import { place_light } from "../../renderer/light/place_light.js";
23
+ import { Renderer } from "../../renderer/Renderer.js";
24
+ import { row_of_entity } from "../../renderer/scene/rows/GPUSceneRows.js";
25
+ import { Scene } from "../../renderer/scene/Scene.js";
26
+ import { load_default_environment } from "../load_default_environment.js";
27
+ import { grid_position, place_character } from "../skinned_blas_refit/place_character.js";
28
+
29
+ /**
30
+ * A skinned character, loaded the way the game loads one, with the pieces that could be making soup
31
+ * put on switches.
32
+ *
33
+ * ## Why this page exists next to `skinned_blas_refit`
34
+ *
35
+ * That page renders the same character and answers a sharp question about the BLAS — does the tree
36
+ * follow the mesh — with `verify()`. It answers that question *numerically*, by holding the tree's
37
+ * root box against the bounds chain's box over the same deformed vertices, and both of those are
38
+ * derived from the clone's vertex region. **Vertices that are wrong in the same way agree with each
39
+ * other perfectly.** So a refit can be correct, `verify()` green, every internal node exactly its
40
+ * children's union — and the picture still soup, because the thing the tree faithfully bounds is
41
+ * itself wrong.
42
+ *
43
+ * This page is that other question, and it is built to bisect it rather than to look good:
44
+ *
45
+ * - **`?skin=off`** — no skin registered. A skinned mesh entity still gets a vertex clone (see
46
+ * {@link GPUMeshSkinningContext#obtain_clone}), but nothing ever deforms it, so what is drawn is
47
+ * the memcpy of the source: the rest pose, under the mesh entity's row — which the prefab put
48
+ * into the skin's own frame. Soup here is the geometry upload — the meshlet copy or its address
49
+ * patch — and has nothing to do with skinning.
50
+ * - **`?skin=bind`** — the skin registered, no clip. The skinning pass runs every frame over a pose
51
+ * that never changes, and the answer it should write is one the CPU knows exactly. `diagnose()`
52
+ * in the console reads the clone's vertices back and says which of them are wrong and in what
53
+ * pattern — see {@link verify_clone_vertices}. Soup here is the skinning machinery.
54
+ * - **`?skin=animated`** — the dance, as `skinned_blas_refit` runs it. Soup only here is the pose:
55
+ * the joint matrices, their range, or the order the skin's joints were resolved in.
56
+ *
57
+ * ## What the bisect found, and what was done
58
+ *
59
+ * `chunk_skin_blend_mesh_local` folds `inverse(mesh.transform_global)` into each joint matrix and
60
+ * converts the product to a dual quaternion, which can only carry a rigid motion. Michelle's
61
+ * positions and inverse-bind matrices are authored in the already-scaled space, so
62
+ * `joint.global × inverse_bind` is the *identity* and the fold was a bare 100×.
63
+ * `dual_quat_from_m4` reads `sqrt(max(0, 1 ± m00 ± m11 ± m22))` over terms of `1 + 100·(…)`, clamps
64
+ * whichever go negative and normalizes the rest, so the rotation it extracted was wrong — and wrong
65
+ * differently per joint, which is what tore the mesh apart. `?skin=bind` was the right character at
66
+ * 1/100 size (one shared matrix, one shared error) and `?skin=animated` was soup.
67
+ *
68
+ * The frame is now the engine's rather than the asset's: compiling a model puts each skinned mesh
69
+ * on its skin's own bind transform, so the fold is the identity at bind and a rigid displacement
70
+ * under a clip, for every shape a glTF file arrives in. See
71
+ * `renderer/animation/SKINNING_FRAME_PLAN.md`.
72
+ *
73
+ * All of which was established without a device — `__soup_blend_probe.spec.js` runs the shader's own
74
+ * blend over this character's own matrices, and the emulator tier in
75
+ * `chunk_skin_blend_mesh_local.spec.js` runs the real WGSL over both shapes. This page is where that
76
+ * is confirmed against pixels, which by then is a formality.
77
+ *
78
+ * ## Why through the asset framework
79
+ *
80
+ * `skinned_blas_refit` calls `load_gltf` directly and parses the file once per character.
81
+ * {@link GLTFSceneBundleAssetLoader} is what the game and the editor register, and it hands **the
82
+ * same bundle** to every caller — so a second character is not a second parse, it is
83
+ * `prefab_instantiate` over the prefab compiled from it once: its own entities, its own skins bound
84
+ * to those entities' joints, its own clips retargeted onto them, sharing the geometry and the
85
+ * inverse-bind matrices. That is a
86
+ * materially different path into the renderer, and running the repro on it means a fix found here
87
+ * is a fix for the engine rather than for one prototype page.
88
+ *
89
+ * Runs under the dev server (`npm run dev`), which serves `test_assets/`.
90
+ *
91
+ * @author Alex Goldring
92
+ * @copyright Company Named Limited (c) 2026
93
+ */
94
+
95
+ /**
96
+ * Served by the dev server out of `test_assets/`, which is gitignored and unpublished. See
97
+ * `test_assets/README.md`.
98
+ *
99
+ * @type {string}
100
+ */
101
+ const CHARACTER_URL = '/test_assets/Michelle.glb';
102
+
103
+ /**
104
+ * How far apart characters stand, in character heights.
105
+ *
106
+ * @type {number}
107
+ */
108
+ const SPACING = 1.1;
109
+
110
+ /**
111
+ * How the skin is driven. The page's whole point is that these are three different questions.
112
+ *
113
+ * @enum {string}
114
+ */
115
+ const SkinMode = {
116
+ /** no skin registered — the clone is never deformed */
117
+ Off: 'off',
118
+ /** skin registered, no clip — the pose the CPU can predict */
119
+ Bind: 'bind',
120
+ /** skin registered, the file's dance bound and playing */
121
+ Animated: 'animated',
122
+ };
123
+
124
+ /**
125
+ * The manager a model load needs, registered the way the game's graphics profile registers it.
126
+ *
127
+ * Not {@link make_playground_asset_manager}: that one is the two-loader subset `load_gltf` needs
128
+ * when a page calls it by hand. Going through {@link load_model_scene_bundle} means the *model* is
129
+ * an asset too, which is the thing this page wants to be on.
130
+ *
131
+ * @returns {Promise<AssetManager>}
132
+ */
133
+ async function make_asset_manager() {
134
+ const manager = new AssetManager({
135
+ context: {},
136
+ executor: new ConcurrentExecutor(),
137
+ });
138
+
139
+ // One instance for both glTF types, as `addModelLoaders` does — the loader holds no per-type
140
+ // state, and the two types are `.glb` and `.gltf` telling themselves apart.
141
+ const models = new GLTFSceneBundleAssetLoader();
142
+
143
+ await manager.registerLoader(GameAssetType.ModelGLTF, models);
144
+ await manager.registerLoader(GameAssetType.ModelGLTF_JSON, models);
145
+
146
+ // the container's `.bin` buffers, and its images — `load_gltf` asks for both back through the
147
+ // manager rather than fetching them itself
148
+ await manager.registerLoader(GameAssetType.ArrayBuffer, new ArrayBufferLoader());
149
+ await manager.registerLoader(GameAssetType.ImageBitmap, new ImageBitmapAssetLoader());
150
+
151
+ manager.startup();
152
+
153
+ return manager;
154
+ }
155
+
156
+ /**
157
+ * Every mesh entity of an instance that a skin deforms: the ones carrying a `MeshSkin`, which is
158
+ * exactly what gets registered and bound.
159
+ *
160
+ * @param {EntityComponentDataset} dataset
161
+ * @param {PrefabInstance} instance
162
+ * @returns {number[]}
163
+ */
164
+ function skinned_meshes_of(dataset, instance) {
165
+ return Array.from(instance.mesh_entities).filter(entity => dataset.getComponent(entity, MeshSkin) !== undefined);
166
+ }
167
+
168
+ /**
169
+ * The world-space box the renderer culls and traces this mesh by — the `meshes` table row, which
170
+ * the skinning pass's bounds-refresh chain rewrites every frame from the vertices it just moved.
171
+ *
172
+ * Not the source geometry's box under the mesh entity's transform: that is the rest pose, and
173
+ * the dance has moved on from it.
174
+ *
175
+ * @param {GPUSceneContext} scene_context
176
+ * @param {number} entity a mesh entity
177
+ * @returns {Promise<{low: Vector3, high: Vector3}>}
178
+ */
179
+ async function world_bounds_of(scene_context, entity) {
180
+ const row = row_of_entity(entity);
181
+
182
+ const record = await scene_context.scene_database.debug_table_read_element('meshes', row);
183
+
184
+ const box = record.bounding_box;
185
+
186
+ return {
187
+ low: new Vector3(box[0], box[1], box[2]),
188
+ high: new Vector3(box[3], box[4], box[5]),
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Stand the page up.
194
+ *
195
+ * @returns {Promise<void>}
196
+ */
197
+ export async function start() {
198
+
199
+ const element = id => document.getElementById(id);
200
+
201
+ const status_element = element('status');
202
+ const stats_element = element('stats');
203
+
204
+ /**
205
+ * @param {string} text
206
+ * @param {string} [kind]
207
+ */
208
+ function set_status(text, kind = '') {
209
+ status_element.textContent = text;
210
+ status_element.className = kind;
211
+ }
212
+
213
+ const canvas = element('view');
214
+
215
+ const context = canvas.getContext('webgpu');
216
+
217
+ if (context === null) {
218
+ set_status(
219
+ 'no WebGPU context — this browser has none, or the page is not a secure context',
220
+ 'fail'
221
+ );
222
+ return;
223
+ }
224
+
225
+ // ---- what the url asks for ------------------------------------------------------------------
226
+
227
+ const parameters = new URLSearchParams(location.search);
228
+
229
+ const count = Math.min(64, Math.max(1, Number.parseInt(parameters.get('count'), 10) || 1));
230
+
231
+ const skin_mode = Object.values(SkinMode).includes(parameters.get('skin'))
232
+ ? parameters.get('skin')
233
+ : SkinMode.Animated;
234
+
235
+ const tracing = parameters.get('trace') === '1';
236
+
237
+ element('character_count').value = String(count);
238
+ element('skin_mode').value = skin_mode;
239
+ element('path_tracing').checked = tracing;
240
+
241
+ // A Scene has no teardown, so changing any of these reloads the page rather than emptying one
242
+ // in place. Every switch is in the url for the same reason a repro is a url: it can be sent.
243
+ function reload_with(changes) {
244
+ const next = new URLSearchParams(location.search);
245
+
246
+ for (const [key, value] of Object.entries(changes)) {
247
+ next.set(key, value);
248
+ }
249
+
250
+ location.search = next.toString();
251
+ }
252
+
253
+ element('respawn').addEventListener('click', () => reload_with({
254
+ count: element('character_count').value,
255
+ skin: element('skin_mode').value,
256
+ }));
257
+
258
+ element('skin_mode').addEventListener('change', () => reload_with({
259
+ skin: element('skin_mode').value,
260
+ }));
261
+
262
+ // ---- the renderer ---------------------------------------------------------------------------
263
+
264
+ const renderer = new Renderer();
265
+
266
+ set_status('acquiring a device…');
267
+
268
+ await renderer.initialize({ context });
269
+
270
+ if (!renderer.graphics.device.features.has('subgroups')) {
271
+ set_status('device has no `subgroups`, which this engine takes as a baseline', 'warn');
272
+ }
273
+
274
+ const camera = new PerspectiveCamera();
275
+
276
+ camera.near = 0.01;
277
+ camera.far = 200;
278
+ camera.fov = 50 * DEG_TO_RAD;
279
+ camera.aspect = renderer.aspect_ratio;
280
+
281
+ const scene = new Scene();
282
+
283
+ // the characters are entities, and the scene's rows are theirs
284
+ const dataset = new EntityComponentDataset();
285
+
286
+ scene.dataset = dataset;
287
+
288
+ // document.body rather than the canvas: the controller starts a keyboard device, and only a
289
+ // focusable element receives key events — body carries tabindex for exactly that.
290
+ const controller = new OrbitalCameraController(camera, document.body);
291
+
292
+ // Rasterized by default. It is the simpler of the two readings — it reads the deformed vertices
293
+ // directly, with no tree in the way — so it is the one to establish the answer on first.
294
+ renderer.feature_path_tracing_enabled = tracing;
295
+ renderer.feature_taa_enabled = false;
296
+
297
+ element('path_tracing').addEventListener('change', event => {
298
+ renderer.feature_path_tracing_enabled = event.target.checked;
299
+ renderer.indicate_view_change();
300
+ });
301
+
302
+ globalThis.renderer = renderer;
303
+ globalThis.scene = scene;
304
+ globalThis.camera = camera;
305
+ globalThis.controller = controller;
306
+
307
+ // ---- the cast -------------------------------------------------------------------------------
308
+
309
+ set_status(`loading ${CHARACTER_URL} …`);
310
+
311
+ const scene_context = renderer.scenes.obtain(scene);
312
+
313
+ /** @type {number[]} the skinned mesh entities */
314
+ const characters = [];
315
+
316
+ try {
317
+ const asset_manager = await make_asset_manager();
318
+
319
+ // One load and one compile, whatever the count: the asset hands out the model itself, and
320
+ // a placement is `prefab_instantiate` over the prefab compiled from it.
321
+ const bundle = await load_model_scene_bundle(asset_manager, CHARACTER_URL);
322
+
323
+ const prefab = prefab_compile(bundle);
324
+
325
+ const animations = scene_context.animation_manager;
326
+
327
+ for (let i = 0; i < count; i++) {
328
+ const { x, z } = grid_position(i, count, SPACING);
329
+
330
+ const { root, instance } = place_character(scene_context, dataset, prefab, x, z, {
331
+ register_animation: skin_mode !== SkinMode.Off,
332
+ });
333
+
334
+ const meshes = skinned_meshes_of(dataset, instance);
335
+
336
+ if (meshes.length === 0) {
337
+ throw new Error(`${CHARACTER_URL} has no skinned mesh`);
338
+ }
339
+
340
+ if (skin_mode === SkinMode.Animated) {
341
+ // One clip, looping, at full weight. The pose accumulator sums every *bound* clip
342
+ // whatever its weight, so binding the T-pose alongside the dance would blend the
343
+ // two — only what should play is registered.
344
+ const clips = dataset.getComponent(root, AnimationClips).clips;
345
+ const clip = clips.find(candidate => /samba|dance/i.test(candidate.name ?? '')) ?? clips[0];
346
+
347
+ if (clip !== undefined) {
348
+ const clip_id = animations.register_clip(clip);
349
+
350
+ animations.set_playback_weight(clip_id, 1);
351
+ animations.set_flags(clip_id, AnimationClipFlags.Loop);
352
+ animations.start(clip_id);
353
+ }
354
+ }
355
+
356
+ characters.push(...meshes);
357
+ }
358
+ } catch (error) {
359
+ set_status(
360
+ `could not load the character (${error.message}). Put it under test_assets/ — `
361
+ + `see test_assets/README.md.`,
362
+ 'fail'
363
+ );
364
+ console.error(error);
365
+ return;
366
+ }
367
+
368
+ if (!scene.lights.elements.some(light => light.isDirectionalLight)) {
369
+ // a glTF character carries no light of its own
370
+ place_light(scene, make_sunlight({ intensity: 6 }));
371
+ }
372
+
373
+ // ---- frames enough for the box to be worth framing on ----------------------------------------
374
+
375
+ set_status('first frames — uploading clones and their trees …');
376
+
377
+ for (let i = 0; i < 8; i++) {
378
+ renderer.render(camera, scene, 1 / 60);
379
+
380
+ await renderer.graphics.device.queue.onSubmittedWorkDone();
381
+ }
382
+
383
+ let low = null;
384
+ let high = null;
385
+
386
+ for (const character of characters) {
387
+ const bounds = await world_bounds_of(scene_context, character);
388
+
389
+ low = low === null ? bounds.low : new Vector3(
390
+ Math.min(low.x, bounds.low.x), Math.min(low.y, bounds.low.y), Math.min(low.z, bounds.low.z)
391
+ );
392
+
393
+ high = high === null ? bounds.high : new Vector3(
394
+ Math.max(high.x, bounds.high.x), Math.max(high.y, bounds.high.y), Math.max(high.z, bounds.high.z)
395
+ );
396
+ }
397
+
398
+ const span = new Vector3(high.x - low.x, high.y - low.y, high.z - low.z);
399
+
400
+ const radius = Math.hypot(span.x, span.y, span.z) * 0.5;
401
+
402
+ camera_frame_sphere(
403
+ controller,
404
+ radius,
405
+ new Vector3((low.x + high.x) / 2, (low.y + high.y) / 2, (low.z + high.z) / 2)
406
+ );
407
+
408
+ controller.movement_speed_scale = radius * 0.2;
409
+
410
+ camera.near = radius * 0.01;
411
+ camera.far = radius * 400;
412
+
413
+ console.log(
414
+ `published box: low=(${low.x.toFixed(4)}, ${low.y.toFixed(4)}, ${low.z.toFixed(4)}) `
415
+ + `high=(${high.x.toFixed(4)}, ${high.y.toFixed(4)}, ${high.z.toFixed(4)}) — `
416
+ + `span ${span.x.toFixed(4)} × ${span.y.toFixed(4)} × ${span.z.toFixed(4)} m`
417
+ );
418
+
419
+ load_default_environment(scene).then(applied => {
420
+ if (applied) {
421
+ renderer.indicate_view_change();
422
+ }
423
+ });
424
+
425
+ // the controller's keyboard device listens on body, and only a focused element gets key events
426
+ document.body.focus();
427
+
428
+ globalThis.characters = characters;
429
+
430
+ // ---- the console half ------------------------------------------------------------------------
431
+
432
+ let animating = skin_mode === SkinMode.Animated;
433
+
434
+ /**
435
+ * The clone's deformed vertices, against the answer the CPU knows for the bind pose.
436
+ *
437
+ * Only meaningful under `?skin=bind`: with a clip playing there is no CPU-side answer to compare
438
+ * against, and with no skin registered there is nothing deforming the clone. It runs in the
439
+ * other two modes anyway rather than refusing — under `?skin=off` a pass says the clone's copy
440
+ * and address patch are right, which is worth knowing on its own.
441
+ */
442
+ globalThis.diagnose = async () => {
443
+ const { verify_clone_vertices } = await import('./verify_clone_vertices.js');
444
+
445
+ const was_animating = animating;
446
+
447
+ animating = false;
448
+
449
+ try {
450
+ return await verify_clone_vertices({ renderer, scene, entity: characters[0] });
451
+ } finally {
452
+ animating = was_animating;
453
+ }
454
+ };
455
+
456
+ set_status(
457
+ `loaded — ${count} × ${CHARACTER_URL}, skin=${skin_mode}. `
458
+ + `\`await diagnose()\` in the console.`,
459
+ 'pass'
460
+ );
461
+
462
+ renderer.indicate_view_change();
463
+
464
+ // ---- frame loop --------------------------------------------------------------------------
465
+
466
+ element('animation').disabled = skin_mode !== SkinMode.Animated;
467
+
468
+ if (skin_mode !== SkinMode.Animated) {
469
+ // there is no clip to freeze; saying so beats a live-looking button that does nothing
470
+ element('animation').textContent = 'No clip playing';
471
+ }
472
+
473
+ element('animation').addEventListener('click', () => {
474
+ animating = !animating;
475
+
476
+ element('animation').textContent = animating ? 'Freeze pose' : 'Resume dance';
477
+
478
+ renderer.path_tracer.clear_history = true;
479
+ renderer.indicate_view_change();
480
+ });
481
+
482
+ const FRAME_RATE_LIMIT = 60;
483
+
484
+ create_frame_loop(time_delta_s => {
485
+ const width = window.innerWidth;
486
+ const height = window.innerHeight;
487
+
488
+ renderer.resize(width, height);
489
+ renderer.pixel_ratio = window.devicePixelRatio;
490
+ camera.aspect = width / height;
491
+
492
+ controller.update();
493
+
494
+ // A frozen pose is a still scene, which is the only condition under which the tracer's
495
+ // accumulator converges — so time stops rather than the frame loop stopping.
496
+ renderer.render(camera, scene, animating ? time_delta_s : 0);
497
+
498
+ stats_element.textContent =
499
+ `${count} × skin=${skin_mode}`
500
+ + `${renderer.feature_path_tracing_enabled ? ' · path traced' : ' · rasterized'}`;
501
+ }, FRAME_RATE_LIMIT);
502
+ }