@woosh/meep-engine 3.44.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 (233) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/core/collection/array/array_sort_insertion_ascending.d.ts +19 -0
  4. package/src/core/collection/array/array_sort_insertion_ascending.d.ts.map +1 -0
  5. package/src/core/collection/array/array_sort_insertion_ascending.js +31 -0
  6. package/src/core/geom/3d/aabb/aabb3_array_round_outward_f32.d.ts +27 -0
  7. package/src/core/geom/3d/aabb/aabb3_array_round_outward_f32.d.ts.map +1 -0
  8. package/src/core/geom/3d/aabb/aabb3_array_round_outward_f32.js +39 -0
  9. package/src/core/geom/3d/aabb/aabb3_from_v3_array.d.ts.map +1 -1
  10. package/src/core/geom/3d/aabb/aabb3_from_v3_array.js +4 -1
  11. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts +2 -2
  12. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -1
  13. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +1 -1
  14. package/src/core/geom/3d/pose/pose3_apply_inverse_point_array.d.ts +21 -0
  15. package/src/core/geom/3d/pose/pose3_apply_inverse_point_array.d.ts.map +1 -0
  16. package/src/core/geom/3d/pose/pose3_apply_inverse_point_array.js +36 -0
  17. package/src/core/geom/3d/pose/pose3_apply_point_array.d.ts +25 -0
  18. package/src/core/geom/3d/pose/pose3_apply_point_array.d.ts.map +1 -0
  19. package/src/core/geom/3d/pose/pose3_apply_point_array.js +40 -0
  20. package/src/core/geom/3d/pose/pose3_compose_array.d.ts +28 -0
  21. package/src/core/geom/3d/pose/pose3_compose_array.d.ts.map +1 -0
  22. package/src/core/geom/3d/pose/pose3_compose_array.js +41 -0
  23. package/src/core/geom/3d/pose/pose3_to_local_array.d.ts +29 -0
  24. package/src/core/geom/3d/pose/pose3_to_local_array.d.ts.map +1 -0
  25. package/src/core/geom/3d/pose/pose3_to_local_array.js +42 -0
  26. package/src/core/geom/3d/quaternion/quat3_clamp_angle_array.d.ts +28 -0
  27. package/src/core/geom/3d/quaternion/quat3_clamp_angle_array.d.ts.map +1 -0
  28. package/src/core/geom/3d/quaternion/quat3_clamp_angle_array.js +66 -0
  29. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_left_array.d.ts +22 -0
  30. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_left_array.d.ts.map +1 -0
  31. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_left_array.js +30 -0
  32. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_right_array.d.ts +23 -0
  33. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_right_array.d.ts.map +1 -0
  34. package/src/core/geom/3d/quaternion/quat3_multiply_conjugate_right_array.js +31 -0
  35. package/src/core/geom/3d/triangle/tri3_compute_normal_area_weighted_array.d.ts +26 -0
  36. package/src/core/geom/3d/triangle/tri3_compute_normal_area_weighted_array.d.ts.map +1 -0
  37. package/src/core/geom/3d/triangle/tri3_compute_normal_area_weighted_array.js +46 -0
  38. package/src/core/geom/vec3/v3_distance_array.d.ts +11 -0
  39. package/src/core/geom/vec3/v3_distance_array.d.ts.map +1 -0
  40. package/src/core/geom/vec3/v3_distance_array.js +19 -0
  41. package/src/core/geom/vec3/v3_normalize_array_checked.d.ts +23 -0
  42. package/src/core/geom/vec3/v3_normalize_array_checked.d.ts.map +1 -0
  43. package/src/core/geom/vec3/v3_normalize_array_checked.js +49 -0
  44. package/src/core/geom/vec3/v3_reject_array.d.ts +15 -0
  45. package/src/core/geom/vec3/v3_reject_array.d.ts.map +1 -0
  46. package/src/core/geom/vec3/v3_reject_array.js +32 -0
  47. package/src/core/graph/csr/csr_build_incidence.d.ts +26 -0
  48. package/src/core/graph/csr/csr_build_incidence.d.ts.map +1 -0
  49. package/src/core/graph/csr/csr_build_incidence.js +71 -0
  50. package/src/core/graph/csr/csr_graph_color_greedy.d.ts +25 -0
  51. package/src/core/graph/csr/csr_graph_color_greedy.d.ts.map +1 -0
  52. package/src/core/graph/csr/csr_graph_color_greedy.js +83 -0
  53. package/src/core/graph/csr/csr_graph_validate_coloring.d.ts +15 -0
  54. package/src/core/graph/csr/csr_graph_validate_coloring.d.ts.map +1 -0
  55. package/src/core/graph/csr/csr_graph_validate_coloring.js +33 -0
  56. package/src/core/math/linalg/matrix_symmetric_3x3_solve_ldlt.d.ts +26 -0
  57. package/src/core/math/linalg/matrix_symmetric_3x3_solve_ldlt.d.ts.map +1 -0
  58. package/src/core/math/linalg/matrix_symmetric_3x3_solve_ldlt.js +71 -0
  59. package/src/core/math/max2_exact.d.ts +15 -0
  60. package/src/core/math/max2_exact.d.ts.map +1 -0
  61. package/src/core/math/max2_exact.js +30 -0
  62. package/src/core/math/min2_exact.d.ts +20 -0
  63. package/src/core/math/min2_exact.d.ts.map +1 -0
  64. package/src/core/math/min2_exact.js +35 -0
  65. package/src/engine/ecs/ik/IKGoalSystem.d.ts.map +1 -1
  66. package/src/engine/ecs/ik/IKGoalSystem.js +3 -19
  67. package/src/engine/ecs/parent/parent_entity_model_of.d.ts +17 -0
  68. package/src/engine/ecs/parent/parent_entity_model_of.d.ts.map +1 -0
  69. package/src/engine/ecs/parent/parent_entity_model_of.js +26 -0
  70. package/src/engine/ecs/transform/t64_read_pose_array.d.ts +15 -0
  71. package/src/engine/ecs/transform/t64_read_pose_array.d.ts.map +1 -0
  72. package/src/engine/ecs/transform/t64_read_pose_array.js +24 -0
  73. package/src/engine/graphics/geometry/geometry_construct_triangle_normal_area_weighted.d.ts +2 -0
  74. package/src/engine/graphics/geometry/geometry_construct_triangle_normal_area_weighted.d.ts.map +1 -1
  75. package/src/engine/graphics/geometry/geometry_construct_triangle_normal_area_weighted.js +10 -21
  76. package/src/engine/graphics3/AnimationGraphSystem.d.ts +5 -0
  77. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
  78. package/src/engine/graphics3/AnimationGraphSystem.js +48 -2
  79. package/src/engine/graphics3/AnimationSystem.d.ts +5 -0
  80. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
  81. package/src/engine/graphics3/AnimationSystem.js +47 -2
  82. package/src/engine/graphics3/register_clips.d.ts +29 -0
  83. package/src/engine/graphics3/register_clips.d.ts.map +1 -1
  84. package/src/engine/graphics3/register_clips.js +52 -0
  85. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +9 -0
  86. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -1
  87. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +3 -1
  88. package/src/engine/physics/cloth/PLAN.md +10 -8
  89. package/src/engine/physics/cloth/build/cloth_bind_in_frame.d.ts +22 -0
  90. package/src/engine/physics/cloth/build/cloth_bind_in_frame.d.ts.map +1 -1
  91. package/src/engine/physics/cloth/build/cloth_bind_in_frame.js +51 -19
  92. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -1
  93. package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +16 -2
  94. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.d.ts.map +1 -1
  95. package/src/engine/physics/cloth/build/cloth_proxy_from_mesh.js +19 -24
  96. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +7 -0
  97. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  98. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +85 -67
  99. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -1
  100. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +19 -101
  101. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +2 -5
  102. package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -1
  103. package/src/engine/physics/cloth/collider/cloth_collider_pack.js +7 -15
  104. package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -1
  105. package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +16 -61
  106. package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +0 -1
  107. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +6 -19
  108. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +1 -2
  109. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
  110. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +3 -12
  111. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  112. package/src/engine/physics/cloth/ecs/ClothWorld.js +40 -130
  113. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  114. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +17 -58
  115. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  116. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +3 -3
  117. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  118. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +44 -104
  119. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts.map +1 -1
  120. package/src/engine/physics/cloth/ecs/cloth_geodesic.js +6 -13
  121. package/src/engine/physics/cloth/ecs/cloth_holds.d.ts.map +1 -1
  122. package/src/engine/physics/cloth/ecs/cloth_holds.js +15 -40
  123. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -1
  124. package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +45 -79
  125. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  126. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +7 -27
  127. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  128. package/src/engine/physics/cloth/ecs/cloth_write_back.js +30 -62
  129. package/src/engine/physics/cloth/playground/cloth_playground_build.d.ts.map +1 -1
  130. package/src/engine/physics/cloth/playground/cloth_playground_build.js +2 -3
  131. package/src/engine/physics/cloth/playground/collide_main.js +2 -28
  132. package/src/engine/physics/cloth/playground/garment_main.js +4 -19
  133. package/src/engine/physics/cloth/playground/main.js +6 -48
  134. package/src/engine/physics/cloth/playground/rig_main.js +947 -955
  135. package/src/engine/physics/cloth/playground/wind_main.js +8 -7
  136. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  137. package/src/engine/physics/cloth/solver/ClothState.js +16 -76
  138. package/src/engine/physics/cloth/solver/cloth_color_particles.d.ts +10 -8
  139. package/src/engine/physics/cloth/solver/cloth_color_particles.d.ts.map +1 -1
  140. package/src/engine/physics/cloth/solver/cloth_color_particles.js +30 -102
  141. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
  142. package/src/engine/physics/cloth/solver/cloth_contact_find.js +3 -2
  143. package/src/engine/physics/cloth/solver/cloth_hold_line.d.ts.map +1 -1
  144. package/src/engine/physics/cloth/solver/cloth_hold_line.js +2 -14
  145. package/src/engine/physics/cloth/solver/cloth_kinetic_energy.d.ts +21 -0
  146. package/src/engine/physics/cloth/solver/cloth_kinetic_energy.d.ts.map +1 -0
  147. package/src/engine/physics/cloth/solver/cloth_kinetic_energy.js +45 -0
  148. package/src/engine/physics/cloth/solver/cloth_particle_coupling.d.ts +32 -0
  149. package/src/engine/physics/cloth/solver/cloth_particle_coupling.d.ts.map +1 -0
  150. package/src/engine/physics/cloth/solver/cloth_particle_coupling.js +133 -0
  151. package/src/engine/physics/cloth/solver/cloth_particle_of_source.d.ts +16 -0
  152. package/src/engine/physics/cloth/solver/cloth_particle_of_source.d.ts.map +1 -0
  153. package/src/engine/physics/cloth/solver/cloth_particle_of_source.js +19 -0
  154. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -1
  155. package/src/engine/physics/cloth/solver/cloth_self_find.js +6 -55
  156. package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
  157. package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +30 -65
  158. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  159. package/src/engine/physics/cloth/solver/cloth_step.js +6 -28
  160. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -1
  161. package/src/engine/physics/cloth/solver/cloth_step_instance.js +2 -3
  162. package/src/engine/physics/cloth/solver/cloth_stretch_residual.d.ts +21 -0
  163. package/src/engine/physics/cloth/solver/cloth_stretch_residual.d.ts.map +1 -0
  164. package/src/engine/physics/cloth/solver/cloth_stretch_residual.js +54 -0
  165. package/src/engine/physics/cloth/solver/cloth_vertex_accumulator.d.ts +15 -0
  166. package/src/engine/physics/cloth/solver/cloth_vertex_accumulator.d.ts.map +1 -1
  167. package/src/engine/physics/cloth/solver/cloth_vertex_accumulator.js +28 -0
  168. package/src/engine/physics/cloth/solver/cloth_worst_tether_ratio.d.ts +30 -0
  169. package/src/engine/physics/cloth/solver/cloth_worst_tether_ratio.d.ts.map +1 -0
  170. package/src/engine/physics/cloth/solver/cloth_worst_tether_ratio.js +56 -0
  171. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -1
  172. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +3 -17
  173. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +2 -1
  174. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -1
  175. package/src/engine/physics/cloth/wind/ClothFluidWind.js +7 -25
  176. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +3 -1
  177. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -1
  178. package/src/engine/physics/cloth/wind/cloth_sample_air.js +40 -62
  179. package/src/engine/physics/ecs/ContactProbeSystem.d.ts.map +1 -1
  180. package/src/engine/physics/ecs/ContactProbeSystem.js +2 -18
  181. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  182. package/src/engine/physics/ecs/PhysicsSystem.js +9 -50
  183. package/src/engine/physics/fluid/ecs/fluid_sample_domain_bounds.d.ts +24 -0
  184. package/src/engine/physics/fluid/ecs/fluid_sample_domain_bounds.d.ts.map +1 -0
  185. package/src/engine/physics/fluid/ecs/fluid_sample_domain_bounds.js +42 -0
  186. package/src/engine/physics/fluid/ecs/fluid_sample_velocity_world_mps.d.ts +25 -0
  187. package/src/engine/physics/fluid/ecs/fluid_sample_velocity_world_mps.d.ts.map +1 -0
  188. package/src/engine/physics/fluid/ecs/fluid_sample_velocity_world_mps.js +40 -0
  189. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.d.ts.map +1 -1
  190. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.js +40 -90
  191. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  192. package/src/engine/physics/narrowphase/capsule_contacts.js +3 -5
  193. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -1
  194. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +2 -14
  195. package/src/engine/physics/narrowphase/hull_sphere_contact.d.ts.map +1 -1
  196. package/src/engine/physics/narrowphase/hull_sphere_contact.js +2 -5
  197. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  198. package/src/engine/physics/narrowphase/narrowphase_step.js +13 -32
  199. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  200. package/src/engine/physics/narrowphase/sphere_box_contact.js +2 -5
  201. package/src/format/texture/basis/transcode/etc1s_to_astc.d.ts.map +1 -1
  202. package/src/format/texture/basis/transcode/etc1s_to_astc.js +2 -1
  203. package/src/shade/playground/image_decode_bench/main.js +2 -1
  204. package/src/shade/playground/skinned_blas_refit/diff_traced_against_rasterized.d.ts.map +1 -1
  205. package/src/shade/playground/skinned_blas_refit/diff_traced_against_rasterized.js +2 -1
  206. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -1
  207. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +2 -1
  208. package/src/shade/playground/ssr_variance/statistics.d.ts.map +1 -1
  209. package/src/shade/playground/ssr_variance/statistics.js +2 -1
  210. package/src/shade/playground/texture_encode/main.d.ts.map +1 -1
  211. package/src/shade/playground/texture_encode/main.js +2 -1
  212. package/src/shade/playground/volumetrics_froxel/measure.d.ts.map +1 -1
  213. package/src/shade/playground/volumetrics_froxel/measure.js +2 -1
  214. package/src/shade/renderer/buffer/table/gpu_database_check_links.d.ts.map +1 -1
  215. package/src/shade/renderer/buffer/table/gpu_database_check_links.js +2 -1
  216. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.d.ts.map +1 -1
  217. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +3 -2
  218. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.d.ts.map +1 -1
  219. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.js +2 -1
  220. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.d.ts.map +1 -1
  221. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.js +2 -1
  222. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.d.ts.map +1 -1
  223. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.js +2 -1
  224. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.d.ts.map +1 -1
  225. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.js +3 -2
  226. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.d.ts.map +1 -1
  227. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.js +2 -1
  228. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts.map +1 -1
  229. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.js +2 -1
  230. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  231. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +3 -2
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -1
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +3 -2
@@ -7,7 +7,8 @@ import { System } from "../ecs/System.js";
7
7
  import { AnimationClips } from "./animation/AnimationClips.js";
8
8
  import { ClipListPlayer } from "./animation/ClipListPlayer.js";
9
9
  import { write_clip_playback } from "./animation/write_clip_playback.js";
10
- import { register_clips, unregister_clips } from "./register_clips.js";
10
+ import { hold_clip_targets, register_clips, release_clip_targets, unregister_clips } from "./register_clips.js";
11
+ import { RowChains } from "./RowChains.js";
11
12
 
12
13
  /**
13
14
  * @type {ClipPlayback[]}
@@ -27,6 +28,10 @@ const NOTHING = Object.freeze([]);
27
28
  * components, whoever put them there — a model `MeshSystem` loaded, or a rig built in code. New
28
29
  * clips are a new component, which this system sees as the entity leaving and coming back.
29
30
  *
31
+ * **Every node the clips key holds a row here** ({@link hold_clip_targets}), from the clips being
32
+ * taken up until they go. A clip binds only once all of them do, and a keyed node outside every skin
33
+ * with nothing drawn under it is held by no other system.
34
+ *
30
35
  * **It is the CPU that plays the animation and the GPU that draws it.** Shade can advance a clip
31
36
  * itself, and this never asks it to — `AnimationClipFlags.Playing` is left clear and every audible
32
37
  * clip's time is written down each frame from the player's own clock. That is I-4: the pose
@@ -51,10 +56,17 @@ export class AnimationSystem extends System {
51
56
  #scene;
52
57
 
53
58
  /**
54
- * @type {Map<number, {player: ClipListPlayer, clips: ShadeAnimationClip[], bound: boolean, clip_registration: ClipRegistration|null, played_slots: Int32Array, written: Float32Array, poses: PosePlayback[]}>}
59
+ * @type {Map<number, {player: ClipListPlayer, clips: ShadeAnimationClip[], bound: boolean, held: boolean, clip_registration: ClipRegistration|null, played_slots: Int32Array, written: Float32Array, poses: PosePlayback[]}>}
55
60
  */
56
61
  #entries = new Map();
57
62
 
63
+ /**
64
+ * The chains of the nodes the clips key. Null until the system has a dataset.
65
+ *
66
+ * @type {RowChains|null}
67
+ */
68
+ #chains = null;
69
+
58
70
  /**
59
71
  * @param {GraphicsEngine} graphics
60
72
  * @param {Scene} scene
@@ -169,6 +181,7 @@ export class AnimationSystem extends System {
169
181
  player: ClipListPlayer.from(component),
170
182
  clips: clips.clips,
171
183
  bound: false,
184
+ held: false,
172
185
  clip_registration: null,
173
186
  played_slots: new Int32Array(0),
174
187
  written: new Float32Array(0),
@@ -186,17 +199,41 @@ export class AnimationSystem extends System {
186
199
 
187
200
  if (entry !== undefined) {
188
201
  this.#release_clips(entry);
202
+
203
+ if (entry.held) {
204
+ release_clip_targets(this.#chains, entry.clips);
205
+
206
+ entry.held = false;
207
+ }
189
208
  }
190
209
 
191
210
  this.#entries.delete(entity);
192
211
  }
193
212
 
213
+ async startup(entityManager) {
214
+ // the scene's rows are this dataset's entities; the first system to place one says so
215
+ const dataset = entityManager.dataset;
216
+ const scene = this.#scene;
217
+
218
+ assert.ok(
219
+ scene.dataset === null || scene.dataset === undefined || scene.dataset === dataset,
220
+ 'the scene is bound to another dataset'
221
+ );
222
+
223
+ scene.dataset = dataset;
224
+
225
+ this.#chains = new RowChains(dataset);
226
+ }
227
+
194
228
  async shutdown(entityManager) {
195
229
  for (const entry of this.#entries.values()) {
196
230
  this.#release_clips(entry);
197
231
  }
198
232
 
199
233
  this.#entries.clear();
234
+
235
+ this.#chains.destroy();
236
+ this.#chains = null;
200
237
  }
201
238
 
202
239
  /**
@@ -328,6 +365,11 @@ export class AnimationSystem extends System {
328
365
  entry.poses = clips.map(() => new PosePlayback());
329
366
 
330
367
  entry.bound = true;
368
+
369
+ // before anything registers, so the clips bind in the update that registers them
370
+ hold_clip_targets(this.#chains, clips);
371
+
372
+ entry.held = true;
331
373
  }
332
374
 
333
375
  /**
@@ -342,6 +384,9 @@ export class AnimationSystem extends System {
342
384
  // that ends while the renderer is being replaced still says so.
343
385
  const manager = context === null ? null : context.animation_manager;
344
386
 
387
+ // a new context after a restart is acquired in whole; until there is one, holds are recorded
388
+ this.#chains.bind(context === null ? null : context.rows);
389
+
345
390
  for (const [entity, entry] of this.#entries) {
346
391
  if (!entry.bound) {
347
392
  this.#bind(entry, entity, ecd);
@@ -1,3 +1,30 @@
1
+ /**
2
+ * Hold a row, with the chain above it, for every entity the clips key.
3
+ *
4
+ * **A clip binds only once every node it keys holds a row**, and until then it is queued and moves
5
+ * nothing. A skin holds its joints' chains and a primitive its own, but a keyed node that is neither
6
+ * — a leaf an exporter left out of the skin, a socket nothing is attached to yet, an animated empty
7
+ * — is held by nothing else, and one such node keeps the whole clip from playing. So the driver that
8
+ * plays the clips holds every node they key, for as long as it has the clips.
9
+ *
10
+ * Every clip of the set, not only the ones registered: the set is replaced and never edited, so its
11
+ * targets are settled once, while which of its clips are registered moves with the list or the graph.
12
+ *
13
+ * One hold per channel, given back one per channel by {@link release_clip_targets}: the chains count
14
+ * holds, so a node keyed by several channels is held until the last of them is released.
15
+ *
16
+ * @param {RowChains} chains
17
+ * @param {ShadeAnimationClip[]} clips their channels target entities
18
+ */
19
+ export function hold_clip_targets(chains: RowChains, clips: ShadeAnimationClip[]): void;
20
+ /**
21
+ * Give back what {@link hold_clip_targets} took for the same clips. Released after the clips are
22
+ * unregistered, so no binding outlives the row it writes.
23
+ *
24
+ * @param {RowChains} chains
25
+ * @param {ShadeAnimationClip[]} clips
26
+ */
27
+ export function release_clip_targets(chains: RowChains, clips: ShadeAnimationClip[]): void;
1
28
  /**
2
29
  * Make some of an entity's clips playable — the ones a driver actually plays, and no others.
3
30
  *
@@ -28,6 +55,7 @@ export function unregister_clips(registration: ClipRegistration): void;
28
55
  /**
29
56
  * @typedef {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} GPUSceneContext
30
57
  * @typedef {import("../../shade/renderer/animation/ShadeAnimationClip.js").ShadeAnimationClip} ShadeAnimationClip
58
+ * @typedef {import("./RowChains.js").RowChains} RowChains
31
59
  */
32
60
  /**
33
61
  * The id a clip has when nobody registered it.
@@ -61,4 +89,5 @@ export class ClipRegistration {
61
89
  }
62
90
  export type GPUSceneContext = import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext;
63
91
  export type ShadeAnimationClip = import("../../shade/renderer/animation/ShadeAnimationClip.js").ShadeAnimationClip;
92
+ export type RowChains = import("./RowChains.js").RowChains;
64
93
  //# sourceMappingURL=register_clips.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"register_clips.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/register_clips.js"],"names":[],"mappings":"AAwCA;;;;;;;;;;;;;;;;GAgBG;AACH,wCALW,eAAe,SACf,kBAAkB,EAAE,SACpB,UAAU,MAAM,CAAC,GACf,gBAAgB,CA6B5B;AAED;;;;;;;GAOG;AACH,+CAFW,gBAAgB,QAqB1B;AA/GD;;;GAGG;AAEH;;;GAGG;AACH,kCAFU,MAAM,CAEsB;AAEtC;;;;;;;GAOG;AACH;IACI;;;;;;;;;OASG;IACH,SAFU,eAAe,GAAC,IAAI,CAEf;IAEf;;OAEG;IACH,KAFU,UAAU,CAEI;CAC3B;8BAnCY,OAAO,+CAA+C,EAAE,eAAe;iCACvE,OAAO,sDAAsD,EAAE,kBAAkB"}
1
+ {"version":3,"file":"register_clips.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/register_clips.js"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;;;;;;GAiBG;AACH,0CAHW,SAAS,SACT,kBAAkB,EAAE,QAa9B;AAED;;;;;;GAMG;AACH,6CAHW,SAAS,SACT,kBAAkB,EAAE,QAa9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wCALW,eAAe,SACf,kBAAkB,EAAE,SACpB,UAAU,MAAM,CAAC,GACf,gBAAgB,CA6B5B;AAED;;;;;;;GAOG;AACH,+CAFW,gBAAgB,QAqB1B;AAnKD;;;;GAIG;AAEH;;;GAGG;AACH,kCAFU,MAAM,CAEsB;AAEtC;;;;;;;GAOG;AACH;IACI;;;;;;;;;OASG;IACH,SAFU,eAAe,GAAC,IAAI,CAEf;IAEf;;OAEG;IACH,KAFU,UAAU,CAEI;CAC3B;8BApCY,OAAO,+CAA+C,EAAE,eAAe;iCACvE,OAAO,sDAAsD,EAAE,kBAAkB;wBACjF,OAAO,gBAAgB,EAAE,SAAS"}
@@ -3,6 +3,7 @@ import { assert } from "../../core/assert.js";
3
3
  /**
4
4
  * @typedef {import("../../shade/renderer/scene/GPUSceneContext.js").GPUSceneContext} GPUSceneContext
5
5
  * @typedef {import("../../shade/renderer/animation/ShadeAnimationClip.js").ShadeAnimationClip} ShadeAnimationClip
6
+ * @typedef {import("./RowChains.js").RowChains} RowChains
6
7
  */
7
8
 
8
9
  /**
@@ -38,6 +39,57 @@ export class ClipRegistration {
38
39
  ids = new Int32Array(0);
39
40
  }
40
41
 
42
+ /**
43
+ * Hold a row, with the chain above it, for every entity the clips key.
44
+ *
45
+ * **A clip binds only once every node it keys holds a row**, and until then it is queued and moves
46
+ * nothing. A skin holds its joints' chains and a primitive its own, but a keyed node that is neither
47
+ * — a leaf an exporter left out of the skin, a socket nothing is attached to yet, an animated empty
48
+ * — is held by nothing else, and one such node keeps the whole clip from playing. So the driver that
49
+ * plays the clips holds every node they key, for as long as it has the clips.
50
+ *
51
+ * Every clip of the set, not only the ones registered: the set is replaced and never edited, so its
52
+ * targets are settled once, while which of its clips are registered moves with the list or the graph.
53
+ *
54
+ * One hold per channel, given back one per channel by {@link release_clip_targets}: the chains count
55
+ * holds, so a node keyed by several channels is held until the last of them is released.
56
+ *
57
+ * @param {RowChains} chains
58
+ * @param {ShadeAnimationClip[]} clips their channels target entities
59
+ */
60
+ export function hold_clip_targets(chains, clips) {
61
+ assert.defined(chains, 'chains');
62
+ assert.isArray(clips, 'clips');
63
+
64
+ for (let i = 0; i < clips.length; i++) {
65
+ const channels = clips[i].channels;
66
+
67
+ for (let j = 0; j < channels.length; j++) {
68
+ chains.hold(channels[j].target);
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Give back what {@link hold_clip_targets} took for the same clips. Released after the clips are
75
+ * unregistered, so no binding outlives the row it writes.
76
+ *
77
+ * @param {RowChains} chains
78
+ * @param {ShadeAnimationClip[]} clips
79
+ */
80
+ export function release_clip_targets(chains, clips) {
81
+ assert.defined(chains, 'chains');
82
+ assert.isArray(clips, 'clips');
83
+
84
+ for (let i = 0; i < clips.length; i++) {
85
+ const channels = clips[i].channels;
86
+
87
+ for (let j = 0; j < channels.length; j++) {
88
+ chains.release(channels[j].target);
89
+ }
90
+ }
91
+ }
92
+
41
93
  /**
42
94
  * Make some of an entity's clips playable — the ones a driver actually plays, and no others.
43
95
  *
@@ -50,4 +50,13 @@ export function fatten_world_aabb_array(aabb: number[] | Float32Array | Float64A
50
50
  * @param {number} dt step size
51
51
  */
52
52
  export function compute_fat_world_aabb(result: number[] | Float32Array | Float64Array, result_offset: number, shape: AbstractShape3D, transform: Transform64, vx: number, vy: number, vz: number, dt: number): void;
53
+ /**
54
+ * Constant inflation applied to a world AABB so a small motion doesn't churn
55
+ * the broadphase. Tuned to Box2D / Bullet conventions. Symmetric: jitter has
56
+ * no preferred direction.
57
+ *
58
+ * Exported because `ClothColliderIndex` fattens its leaves by the same slack, for the same reason.
59
+ * @type {number}
60
+ */
61
+ export const FAT_LINEAR: number;
53
62
  //# sourceMappingURL=compute_fat_world_aabb.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compute_fat_world_aabb.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/broadphase/compute_fat_world_aabb.js"],"names":[],"mappings":"AAuBA;;;;;;;;GAQG;AACH,2CALW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,wDAchB;AAED;;;;;;;;;;;;;GAaG;AACH,wCAPW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,UAClC,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAUhB;AAKD;;;;;;;;;;GAUG;AACH,8CANW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,UAClC,MAAM,YACN,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,mBAClC,MAAM,MACN,MAAM,QAkBhB;AAED;;;;;;;;;;;;;GAaG;AACH,+CATW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,sDAGN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAShB"}
1
+ {"version":3,"file":"compute_fat_world_aabb.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/broadphase/compute_fat_world_aabb.js"],"names":[],"mappings":"AAyBA;;;;;;;;GAQG;AACH,2CALW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,wDAchB;AAED;;;;;;;;;;;;;GAaG;AACH,wCAPW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,UAClC,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAUhB;AAKD;;;;;;;;;;GAUG;AACH,8CANW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,UAClC,MAAM,YACN,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,mBAClC,MAAM,MACN,MAAM,QAkBhB;AAED;;;;;;;;;;;;;GAaG;AACH,+CATW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,iBAClC,MAAM,sDAGN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QAShB;AAvHD;;;;;;;GAOG;AACH,yBAFU,MAAM,CAEe"}
@@ -6,9 +6,11 @@ const scratch_local = new Float64Array(6);
6
6
  * Constant inflation applied to a world AABB so a small motion doesn't churn
7
7
  * the broadphase. Tuned to Box2D / Bullet conventions. Symmetric: jitter has
8
8
  * no preferred direction.
9
+ *
10
+ * Exported because `ClothColliderIndex` fattens its leaves by the same slack, for the same reason.
9
11
  * @type {number}
10
12
  */
11
- const FAT_LINEAR = 0.05;
13
+ export const FAT_LINEAR = 0.05;
12
14
 
13
15
  /**
14
16
  * Per-second-of-velocity inflation. Two seconds of motion is a generous
@@ -270,7 +270,7 @@ export class ClothCollider {
270
270
  friction_scale = 1; // multiplies the entity's Collider#friction
271
271
  layer = 1; // RigidBody semantics, and see below
272
272
  mask = 0xFFFFFFFF;
273
- flags = 0; // ClothColliderFlags.Static
273
+ flags = 0; // ClothColliderFlags: none defined; bit 0 was Static, retired
274
274
  }
275
275
  ```
276
276
 
@@ -675,10 +675,12 @@ bundles — the engine ships raw source, and a check moved out of `assert.*` sto
675
675
  ### 5.4 Colouring
676
676
 
677
677
  The particle graph is coloured so no constraint touches two particles of the same colour, using the
678
- existing `core/graph/coloring/colorizeGraphGreedy.js` and checked by the existing
679
- `validateGraphColoring.js`. Bending stencils span four particles, so the graph is the mesh graph plus
680
- the bend diagonals — around 8–10 colours for typical garment topology. Colouring runs once, on a
681
- `Graph` object, and is baked into the `ClothProxy` as colour ranges over a permuted particle order;
678
+ greedy colouring in `core/graph/csr/csr_graph_color_greedy.js` and checked by
679
+ `csr_graph_validate_coloring.js`, over the deduplicated particle-coupling CSR that
680
+ `solver/cloth_particle_coupling.js` builds (the object `Graph` route was O(n²·degree): 134 ms → 3.5 ms
681
+ on a 64×64 sheet, same colours). Bending stencils span four particles, so the graph is the mesh graph plus
682
+ the bend diagonals — around 8–10 colours for typical garment topology. Colouring runs once, on that
683
+ CSR, and is baked into the `ClothProxy` as colour ranges over a permuted particle order;
682
684
  for a chain it runs at link time on a graph of a few dozen nodes. Nothing colours per step and the
683
685
  runtime never sees a graph object.
684
686
 
@@ -1620,7 +1622,7 @@ For a **rigged** garment, only steps 5–8 run, over the joint graph rather than
1620
1622
  (`cloth_rig.spec.js`).
1621
1623
  6. Bake `d_geo` — geodesic distance from every particle to the fixed set. This one array drives the
1622
1624
  tether row, the slack profile, and the pinning, so the three cannot disagree.
1623
- 7. Colour the particle graph (`colorizeGraphGreedy`, checked by `validateGraphColoring`), and permute
1625
+ 7. Colour the particle graph (`csr_graph_color_greedy`, checked by `csr_graph_validate_coloring`), and permute
1624
1626
  the particle order so each colour is a contiguous range.
1625
1627
  8. Resolve joint **names to indices** into `Skin#joints`, using `SceneBundleInstance#nodes`. This is
1626
1628
  the only place a name appears anywhere in the system.
@@ -1803,9 +1805,9 @@ What this design takes from the engine rather than building:
1803
1805
  | Mesh topology, adjacency, bending stencils | `core/geom/3d/topology/struct/binary/` — `BinaryTopology`, `bt_edge_read_diamond`, `bt_edge_is_boundary`, `bt_collect_boundary_segments` |
1804
1806
  | Proxy decimation | `bt_mesh_simplify_by_error`, `bt_mesh_compute_vertex_quadrics` |
1805
1807
  | Mesh cleanup | `geometry_deduplicate_vertices` |
1806
- | Graph colouring | `core/graph/coloring/colorizeGraphGreedy.js`, `validateGraphColoring.js` |
1808
+ | Graph colouring | `core/graph/csr/csr_graph_color_greedy.js`, `csr_graph_validate_coloring.js` |
1807
1809
  | Collider shapes and their SDFs | `core/geom/3d/shape/*` — closed-form `signed_distance_at_point` on every primitive |
1808
- | Collider AABBs, pose caching | `core/geom/3d/aabb/*`, `aabb3_matrix4_project` |
1810
+ | Collider AABBs, pose caching | `core/geom/3d/aabb/*`, `aabb3_transform_oriented_array` |
1809
1811
  | Collider ECS wiring | `Collider`, `ColliderFlags`, `ColliderObserverSystem` (as the pattern) |
1810
1812
  | "Opt a collider into a second simulation" | `FluidObstacle` + `FluidObstacleSystem`, `AcousticBody` |
1811
1813
  | Collider index shape | `AcousticOccluderIndex` + `OccluderBody` |
@@ -10,6 +10,12 @@
10
10
  * bind pose — so that a re-seed reads what was authored rather than what the
11
11
  * solver left on the joints.
12
12
  *
13
+ * Rigid, as every pose cloth holds is: a local scale is not applied. That is
14
+ * not a convention this can opt out of alone — the write-back is rigid too,
15
+ * and the GPU applies a parent's scale on top of what cloth writes, so a
16
+ * scaled bake would draw a scaled link twice over. A builder refuses a rig
17
+ * where it would matter, with {@link cloth_bind_scaled_above}.
18
+ *
13
19
  * @author Alex Goldring
14
20
  * @copyright Company Named Limited (c) 2026
15
21
  *
@@ -18,5 +24,21 @@
18
24
  * @returns {function(number): Float64Array}
19
25
  */
20
26
  export function cloth_bind_in_frame(prefab: Prefab, frame: number): (arg0: number) => Float64Array;
27
+ /**
28
+ * The nearest node above `node`, up to and including the prefab's root, whose
29
+ * rest scale is not one; or -1.
30
+ *
31
+ * Cloth simulates and writes back rigid poses, and everything under a scaled
32
+ * node is drawn scaled: a link below one is simulated at its authored length and
33
+ * drawn at that times the scale, and a scale above the frame draws the whole
34
+ * garment at another size than the one its colliders meet. Measured, a 10 cm
35
+ * chain under a joint scaled by 2: simulated links 0.1, drawn 0.2. A node's own
36
+ * scale moves nothing cloth places, so it is not asked about.
37
+ *
38
+ * @param {Prefab} prefab
39
+ * @param {number} node
40
+ * @returns {number}
41
+ */
42
+ export function cloth_bind_scaled_above(prefab: Prefab, node: number): number;
21
43
  export type Prefab = import("../../../graphics3/prefab/Prefab.js").Prefab;
22
44
  //# sourceMappingURL=cloth_bind_in_frame.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloth_bind_in_frame.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/cloth_bind_in_frame.js"],"names":[],"mappings":"AAOA;;GAEG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,4CAJW,MAAM,SACN,MAAM,UACK,MAAM,KAAG,YAAY,CAiD1C;qBAlEY,OAAO,qCAAqC,EAAE,MAAM"}
1
+ {"version":3,"file":"cloth_bind_in_frame.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/cloth_bind_in_frame.js"],"names":[],"mappings":"AAOA;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,4CAJW,MAAM,SACN,MAAM,UACK,MAAM,KAAG,YAAY,CAqC1C;AASD;;;;;;;;;;;;;;GAcG;AACH,gDAJW,MAAM,QACN,MAAM,GACJ,MAAM,CAgBlB;qBAlGY,OAAO,qCAAqC,EAAE,MAAM"}
@@ -1,7 +1,7 @@
1
- import { quat3_multiply } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
2
- import { v3_quaternion_apply } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
1
+ import { pose3_compose_array } from "../../../../core/geom/3d/pose/pose3_compose_array.js";
3
2
  import {
4
3
  TRANSFORM64_ROTATION_OFFSET,
4
+ TRANSFORM64_SCALE_OFFSET,
5
5
  TRANSFORM64_TRANSLATION_OFFSET
6
6
  } from "../../../ecs/transform/Transform64.js";
7
7
 
@@ -18,6 +18,12 @@ import {
18
18
  * bind pose — so that a re-seed reads what was authored rather than what the
19
19
  * solver left on the joints.
20
20
  *
21
+ * Rigid, as every pose cloth holds is: a local scale is not applied. That is
22
+ * not a convention this can opt out of alone — the write-back is rigid too,
23
+ * and the GPU applies a parent's scale on top of what cloth writes, so a
24
+ * scaled bake would draw a scaled link twice over. A builder refuses a rig
25
+ * where it would matter, with {@link cloth_bind_scaled_above}.
26
+ *
21
27
  * @author Alex Goldring
22
28
  * @copyright Company Named Limited (c) 2026
23
29
  *
@@ -45,25 +51,13 @@ export function cloth_bind_in_frame(prefab, frame) {
45
51
 
46
52
  answer = new Float64Array(7);
47
53
 
48
- v3_quaternion_apply(
54
+ pose3_compose_array(
49
55
  answer, 0,
50
- local[TRANSFORM64_TRANSLATION_OFFSET],
51
- local[TRANSFORM64_TRANSLATION_OFFSET + 1],
52
- local[TRANSFORM64_TRANSLATION_OFFSET + 2],
53
- above[3], above[4], above[5], above[6]
54
- );
55
-
56
- answer[0] += above[0];
57
- answer[1] += above[1];
58
- answer[2] += above[2];
59
-
60
- quat3_multiply(
61
56
  answer, 3,
62
- above[3], above[4], above[5], above[6],
63
- local[TRANSFORM64_ROTATION_OFFSET],
64
- local[TRANSFORM64_ROTATION_OFFSET + 1],
65
- local[TRANSFORM64_ROTATION_OFFSET + 2],
66
- local[TRANSFORM64_ROTATION_OFFSET + 3]
57
+ above, 0,
58
+ above, 3,
59
+ local, TRANSFORM64_TRANSLATION_OFFSET,
60
+ local, TRANSFORM64_ROTATION_OFFSET
67
61
  );
68
62
 
69
63
  known.set(node, answer);
@@ -73,3 +67,41 @@ export function cloth_bind_in_frame(prefab, frame) {
73
67
 
74
68
  return pose;
75
69
  }
70
+
71
+ /**
72
+ * How far a rest scale may be from one before it is a scale: the tolerance
73
+ * `prefab_normalize_skin_frames` holds a skin's frame to, for the same reason.
74
+ * @type {number}
75
+ */
76
+ const SCALE_TOLERANCE = 1e-4;
77
+
78
+ /**
79
+ * The nearest node above `node`, up to and including the prefab's root, whose
80
+ * rest scale is not one; or -1.
81
+ *
82
+ * Cloth simulates and writes back rigid poses, and everything under a scaled
83
+ * node is drawn scaled: a link below one is simulated at its authored length and
84
+ * drawn at that times the scale, and a scale above the frame draws the whole
85
+ * garment at another size than the one its colliders meet. Measured, a 10 cm
86
+ * chain under a joint scaled by 2: simulated links 0.1, drawn 0.2. A node's own
87
+ * scale moves nothing cloth places, so it is not asked about.
88
+ *
89
+ * @param {Prefab} prefab
90
+ * @param {number} node
91
+ * @returns {number}
92
+ */
93
+ export function cloth_bind_scaled_above(prefab, node) {
94
+ const parent = prefab.parent;
95
+
96
+ for (let above = parent[node]; above >= 0; above = parent[above]) {
97
+ const local = prefab.local[above];
98
+
99
+ if (Math.abs(local[TRANSFORM64_SCALE_OFFSET] - 1) > SCALE_TOLERANCE
100
+ || Math.abs(local[TRANSFORM64_SCALE_OFFSET + 1] - 1) > SCALE_TOLERANCE
101
+ || Math.abs(local[TRANSFORM64_SCALE_OFFSET + 2] - 1) > SCALE_TOLERANCE) {
102
+ return above;
103
+ }
104
+ }
105
+
106
+ return -1;
107
+ }
@@ -1 +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":"AAGA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,gDARW,MAAM,QACN,IAAI,eAEJ,MAAM,EAAE;IAES,IAAI,GAArB,MAAM;IACJ,qBAAqB,CAgQjC;qBAjUY,OAAO,qCAAqC,EAAE,MAAM;mBACpD,OAAO,8CAA8C,EAAE,IAAI;;;;;;;;WAwC1D,MAAM;;;;eACN,UAAU;;;;;;WAKV,UAAU,GAAC,IAAI;;;;YACf,eAAe,EAAE;;;;WACjB,MAAM;;2BArDO,iBAAiB"}
1
+ {"version":3,"file":"cloth_proxy_from_joints.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/cloth_proxy_from_joints.js"],"names":[],"mappings":"AAIA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,gDARW,MAAM,QACN,IAAI,eAEJ,MAAM,EAAE;IAES,IAAI,GAArB,MAAM;IACJ,qBAAqB,CA6QjC;qBA9UY,OAAO,qCAAqC,EAAE,MAAM;mBACpD,OAAO,8CAA8C,EAAE,IAAI;;;;;;;;WAwC1D,MAAM;;;;eACN,UAAU;;;;;;WAKV,UAAU,GAAC,IAAI;;;;YACf,eAAe,EAAE;;;;WACjB,MAAM;;2BAtDO,iBAAiB"}
@@ -1,5 +1,6 @@
1
1
  import { ClothProxy } from "./ClothProxy.js";
2
- import { cloth_bind_in_frame } from "./cloth_bind_in_frame.js";
2
+ import { cloth_bind_in_frame, cloth_bind_scaled_above } from "./cloth_bind_in_frame.js";
3
+ import { number_compare_ascending } from "../../../../core/primitives/numbers/number_compare_ascending.js";
3
4
 
4
5
  /**
5
6
  * @typedef {import("../../../graphics3/prefab/Prefab.js").Prefab} Prefab
@@ -161,7 +162,7 @@ export function cloth_proxy_from_joints(prefab, skin, joint_names, { name = "" }
161
162
  }
162
163
  }
163
164
 
164
- hung_from.sort((a, b) => a - b);
165
+ hung_from.sort(number_compare_ascending);
165
166
 
166
167
  // The frame: the nearest skin joint above every bone the garment hangs from
167
168
  // — for one bone, the bone itself.
@@ -236,6 +237,19 @@ export function cloth_proxy_from_joints(prefab, skin, joint_names, { name = "" }
236
237
 
237
238
  const particle_name = [];
238
239
 
240
+ for (let i = 0; i < particle_count; i++) {
241
+ const scaled = cloth_bind_scaled_above(prefab, ordered[i]);
242
+
243
+ if (scaled >= 0) {
244
+ const local = prefab.local[scaled];
245
+
246
+ return refuse(
247
+ `${name}: "${name_of[scaled]}", above "${name_of[ordered[i]]}", is scaled by (${local.scale_x}, ${local.scale_y}, ${local.scale_z}) at rest;`
248
+ + ` cloth poses its joints rigidly, so it would simulate a different garment from the one drawn`
249
+ );
250
+ }
251
+ }
252
+
239
253
  const pose = cloth_bind_in_frame(prefab, frame_node);
240
254
 
241
255
  for (let i = 0; i < particle_count; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"cloth_proxy_from_mesh.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/cloth_proxy_from_mesh.js"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;;GAUG;AACH,8CARW,MAAM,QACN,IAAI,eACJ,MAAM,EAAE,aACR,UAAU,GAAC,MAAM,EAAE;IAEF,IAAI,GAArB,MAAM;IACJ,qBAAqB,CA8LjC;qBApPY,OAAO,qCAAqC,EAAE,MAAM;mBACpD,OAAO,8CAA8C,EAAE,IAAI;oCAC3D,OAAO,8BAA8B,EAAE,qBAAqB"}
1
+ {"version":3,"file":"cloth_proxy_from_mesh.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/build/cloth_proxy_from_mesh.js"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;;GAUG;AACH,8CARW,MAAM,QACN,IAAI,eACJ,MAAM,EAAE,aACR,UAAU,GAAC,MAAM,EAAE;IAEF,IAAI,GAArB,MAAM;IACJ,qBAAqB,CA8MjC;qBApQY,OAAO,qCAAqC,EAAE,MAAM;mBACpD,OAAO,8CAA8C,EAAE,IAAI;oCAC3D,OAAO,8BAA8B,EAAE,qBAAqB"}
@@ -1,5 +1,6 @@
1
+ import { tri3_area } from "../../../../core/geom/3d/triangle/tri3_area.js";
1
2
  import { ClothProxy } from "./ClothProxy.js";
2
- import { cloth_bind_in_frame } from "./cloth_bind_in_frame.js";
3
+ import { cloth_bind_in_frame, cloth_bind_scaled_above } from "./cloth_bind_in_frame.js";
3
4
 
4
5
  /**
5
6
  * @typedef {import("../../../graphics3/prefab/Prefab.js").Prefab} Prefab
@@ -140,6 +141,18 @@ export function cloth_proxy_from_mesh(prefab, skin, joint_names, triangles, { na
140
141
  return refuse(`${name}: ${triangles.length} triangle indices is not a whole number of triangles`);
141
142
  }
142
143
 
144
+ // Every vertex joint hangs from the frame, so one walk up from any of them asks the lot.
145
+ const scaled = cloth_bind_scaled_above(prefab, node[0]);
146
+
147
+ if (scaled >= 0) {
148
+ const local = prefab.local[scaled];
149
+
150
+ return refuse(
151
+ `${name}: "${name_of[scaled]}", above "${name_of[node[0]]}", is scaled by (${local.scale_x}, ${local.scale_y}, ${local.scale_z}) at rest;`
152
+ + ` cloth poses its joints rigidly, so it would simulate a different garment from the one drawn`
153
+ );
154
+ }
155
+
143
156
  const pose = cloth_bind_in_frame(prefab, frame_node);
144
157
 
145
158
  const rest_position = new Float32Array(particle_count * 3);
@@ -176,7 +189,11 @@ export function cloth_proxy_from_mesh(prefab, skin, joint_names, triangles, { na
176
189
  }
177
190
  }
178
191
 
179
- if (twice_area(rest_position, i0, i1, i2) <= 0) {
192
+ if (tri3_area(
193
+ rest_position[i0 * 3], rest_position[i0 * 3 + 1], rest_position[i0 * 3 + 2],
194
+ rest_position[i1 * 3], rest_position[i1 * 3 + 1], rest_position[i1 * 3 + 2],
195
+ rest_position[i2 * 3], rest_position[i2 * 3 + 1], rest_position[i2 * 3 + 2]
196
+ ) <= 0) {
180
197
  return refuse(
181
198
  `${name}: triangle ${t} ("${joint_names[i0]}", "${joint_names[i1]}", "${joint_names[i2]}") has no area,`
182
199
  + ` so no normal and no hinge across it`
@@ -248,28 +265,6 @@ export function cloth_proxy_from_mesh(prefab, skin, joint_names, triangles, { na
248
265
  };
249
266
  }
250
267
 
251
- /**
252
- * Twice the area of a triangle of the rest pose.
253
- *
254
- * @param {Float32Array} position
255
- * @param {number} a
256
- * @param {number} b
257
- * @param {number} c
258
- * @returns {number}
259
- * @private
260
- */
261
- function twice_area(position, a, b, c) {
262
- const ux = position[b * 3] - position[a * 3];
263
- const uy = position[b * 3 + 1] - position[a * 3 + 1];
264
- const uz = position[b * 3 + 2] - position[a * 3 + 2];
265
-
266
- const vx = position[c * 3] - position[a * 3];
267
- const vy = position[c * 3 + 1] - position[a * 3 + 1];
268
- const vz = position[c * 3 + 2] - position[a * 3 + 2];
269
-
270
- return Math.hypot(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx);
271
- }
272
-
273
268
  /**
274
269
  * @param {string} error
275
270
  * @returns {ClothProxyBuildResult}
@@ -164,6 +164,13 @@ export class ClothColliderIndex {
164
164
  * ignores it, so a distance in the shape's local frame is a distance in
165
165
  * world space.
166
166
  *
167
+ * A pose equal to the one the record already holds is not refit: its
168
+ * bounds were fit to exactly this pair of poses when either last changed —
169
+ * here, in {@link placePose}, or in {@link advance} — so the refit would
170
+ * write what is there. That is what a collider that never moves costs, and
171
+ * why nothing has to say it will not: 1.43 µs per collider per step
172
+ * refitting, 0.42 µs skipping, of which {@link advance} is 0.32.
173
+ *
167
174
  * @param {number} handle
168
175
  * @param {number} tx
169
176
  * @param {number} ty
@@ -1 +1 @@
1
- {"version":3,"file":"ClothColliderIndex.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/collider/ClothColliderIndex.js"],"names":[],"mappings":"AA2CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH;IAEI;;;;;;OAMG;IACH,OAFU,YAAY,CAEM;IAE5B;;;;;OAKG;IACH,MAFU,UAAU,CAEK;IAEzB;;;;OAIG;IACH,OAFU,MAAM,CAEN;IAEV;;;;;;OAMG;IACH,eAFU,MAAM,CAEE;IAElB;;;;;;OAMG;IACH,qBAFU,MAAM,CAEQ;IAoFxB;;;;;;;;;;;OAWG;IACH,sCAPW,MAAM,aACN,MAAM,SACN,MAAM,QACN,MAAM,UACN,MAAM,GACJ,MAAM,CA8DlB;IAED;;;;;;;OAOG;IACH,eAHW,MAAM,GACJ,IAAI,CAoChB;IAED;;;;OAIG;IACH,eAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,mBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;OAIG;IACH,WAFa,IAAI,CAuDhB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAVW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,IAAI,CA8BhB;IAwID;;;;;;;;;;;;;;;OAeG;IACH,kBAVW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,IAAI,CAmBhB;IAED;;;;;;;;OAQG;IACH,mBALW,MAAM,YACN,MAAM,aACN,MAAM,GACJ,IAAI,CAoBhB;IAED;;;;;OAKG;IACH,kBALW,MAAM,SACN,MAAM,QACN,MAAM,GACJ,IAAI,CAWhB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAdW,MAAM,EAAE,SACR,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,QACN,MAAM,YACN,OAAO,GAGL,MAAM,CAWlB;IAED;;;;;;;;;;;OAWG;IACH,gBARW,MAAM,EAAE,OACR,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,cAClC,MAAM,SACN,MAAM,QACN,MAAM,WACN,OAAO,GACL,MAAM,CA6DlB;;CAyGJ"}
1
+ {"version":3,"file":"ClothColliderIndex.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/collider/ClothColliderIndex.js"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH;IAEI;;;;;;OAMG;IACH,OAFU,YAAY,CAEM;IAE5B;;;;;OAKG;IACH,MAFU,UAAU,CAEK;IAEzB;;;;OAIG;IACH,OAFU,MAAM,CAEN;IAEV;;;;;;OAMG;IACH,eAFU,MAAM,CAEE;IAElB;;;;;;OAMG;IACH,qBAFU,MAAM,CAEQ;IAoFxB;;;;;;;;;;;OAWG;IACH,sCAPW,MAAM,aACN,MAAM,SACN,MAAM,QACN,MAAM,UACN,MAAM,GACJ,MAAM,CA8DlB;IAED;;;;;;;OAOG;IACH,eAHW,MAAM,GACJ,IAAI,CAkChB;IAED;;;;OAIG;IACH,eAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,mBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;OAIG;IACH,WAFa,IAAI,CA4ChB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,gBAVW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,IAAI,CAqBhB;IAsLD;;;;;;;;;;;;;;;OAeG;IACH,kBAVW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,IAAI,CAqBhB;IAED;;;;;;;;OAQG;IACH,mBALW,MAAM,YACN,MAAM,aACN,MAAM,GACJ,IAAI,CAoBhB;IAED;;;;;OAKG;IACH,kBALW,MAAM,SACN,MAAM,QACN,MAAM,GACJ,IAAI,CAWhB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAdW,MAAM,EAAE,SACR,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,QACN,MAAM,YACN,OAAO,GAGL,MAAM,CAWlB;IAED;;;;;;;;;;;OAWG;IACH,gBARW,MAAM,EAAE,OACR,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,cAClC,MAAM,SACN,MAAM,QACN,MAAM,WACN,OAAO,GACL,MAAM,CA6DlB;;CAyGJ"}