@woosh/meep-engine 3.11.3 → 3.12.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 (401) hide show
  1. package/README.md +14 -18
  2. package/editor/SelectionVisualizer.js +127 -127
  3. package/editor/entity_world_bounds.js +27 -27
  4. package/editor/particles/rebuildParticleEmitter.js +31 -31
  5. package/editor/process/SymbolicDisplayProcess.js +1 -1
  6. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  7. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  8. package/editor/prototypeEditorShell.js +156 -156
  9. package/editor/selection/editor_pick.js +130 -130
  10. package/editor/selection/screen_rect_frustum.js +119 -119
  11. package/editor/tools/v2/TransformControls.js +971 -971
  12. package/package.json +1 -1
  13. package/src/engine/Engine.d.ts +3 -3
  14. package/src/engine/Engine.d.ts.map +1 -1
  15. package/src/engine/Engine.js +726 -726
  16. package/src/engine/EngineHarness.d.ts +1 -1
  17. package/src/engine/EngineHarness.js +590 -590
  18. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  19. package/src/engine/ecs/terrain/ecs/BuildLightTexture.d.ts +1 -1
  20. package/src/engine/ecs/terrain/ecs/BuildLightTexture.js +27 -27
  21. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +1 -5
  22. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  23. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  24. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  25. package/src/engine/graphics/ecs/camera/camera_find_active.d.ts +1 -1
  26. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  27. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.d.ts +1 -1
  28. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  29. package/src/engine/graphics/ecs/water/Water.d.ts +1 -1
  30. package/src/engine/graphics/ecs/water/Water.js +104 -104
  31. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +1 -1
  32. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1 -1
  33. package/src/engine/graphics3/AnimationGraphSystem.d.ts +65 -0
  34. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -0
  35. package/src/engine/graphics3/AnimationGraphSystem.js +329 -0
  36. package/src/engine/graphics3/AnimationGraphSystem3.d.ts +6 -61
  37. package/src/engine/graphics3/AnimationGraphSystem3.d.ts.map +1 -1
  38. package/src/engine/graphics3/AnimationGraphSystem3.js +6 -325
  39. package/src/engine/graphics3/AnimationSystem.d.ts +76 -0
  40. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -0
  41. package/src/engine/graphics3/AnimationSystem.js +352 -0
  42. package/src/engine/graphics3/AnimationSystem3.d.ts +6 -72
  43. package/src/engine/graphics3/AnimationSystem3.d.ts.map +1 -1
  44. package/src/engine/graphics3/AnimationSystem3.js +6 -348
  45. package/src/engine/graphics3/CameraSystem.d.ts +49 -0
  46. package/src/engine/graphics3/CameraSystem.d.ts.map +1 -0
  47. package/src/engine/graphics3/CameraSystem.js +170 -0
  48. package/src/engine/graphics3/CameraSystem3.d.ts +6 -45
  49. package/src/engine/graphics3/CameraSystem3.d.ts.map +1 -1
  50. package/src/engine/graphics3/CameraSystem3.js +6 -166
  51. package/src/engine/graphics3/DebugDrawSystem.d.ts +72 -0
  52. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -0
  53. package/src/engine/graphics3/DebugDrawSystem.js +205 -0
  54. package/src/engine/graphics3/DebugDrawSystem3.d.ts +6 -68
  55. package/src/engine/graphics3/DebugDrawSystem3.d.ts.map +1 -1
  56. package/src/engine/graphics3/DebugDrawSystem3.js +6 -201
  57. package/src/engine/graphics3/DecalSystem.d.ts +81 -0
  58. package/src/engine/graphics3/DecalSystem.d.ts.map +1 -0
  59. package/src/engine/graphics3/DecalSystem.js +441 -0
  60. package/src/engine/graphics3/DecalSystem3.d.ts +6 -77
  61. package/src/engine/graphics3/DecalSystem3.d.ts.map +1 -1
  62. package/src/engine/graphics3/DecalSystem3.js +6 -437
  63. package/src/engine/graphics3/FogOfWarSystem.d.ts +74 -0
  64. package/src/engine/graphics3/FogOfWarSystem.d.ts.map +1 -0
  65. package/src/engine/graphics3/FogOfWarSystem.js +265 -0
  66. package/src/engine/graphics3/FogOfWarSystem3.d.ts +6 -70
  67. package/src/engine/graphics3/FogOfWarSystem3.d.ts.map +1 -1
  68. package/src/engine/graphics3/FogOfWarSystem3.js +6 -261
  69. package/src/engine/graphics3/GraphicsEngine.d.ts +340 -0
  70. package/src/engine/graphics3/GraphicsEngine.d.ts.map +1 -0
  71. package/src/engine/graphics3/GraphicsEngine.js +790 -0
  72. package/src/engine/graphics3/GraphicsEngine3.d.ts +6 -336
  73. package/src/engine/graphics3/GraphicsEngine3.d.ts.map +1 -1
  74. package/src/engine/graphics3/GraphicsEngine3.js +6 -786
  75. package/src/engine/graphics3/HighlightSystem.d.ts +82 -0
  76. package/src/engine/graphics3/HighlightSystem.d.ts.map +1 -0
  77. package/src/engine/graphics3/HighlightSystem.js +241 -0
  78. package/src/engine/graphics3/HighlightSystem3.d.ts +6 -78
  79. package/src/engine/graphics3/HighlightSystem3.d.ts.map +1 -1
  80. package/src/engine/graphics3/HighlightSystem3.js +6 -237
  81. package/src/engine/graphics3/LightSystem.d.ts +40 -0
  82. package/src/engine/graphics3/LightSystem.d.ts.map +1 -0
  83. package/src/engine/graphics3/LightSystem.js +211 -0
  84. package/src/engine/graphics3/LightSystem3.d.ts +6 -36
  85. package/src/engine/graphics3/LightSystem3.d.ts.map +1 -1
  86. package/src/engine/graphics3/LightSystem3.js +6 -207
  87. package/src/engine/graphics3/MeshSystem.d.ts +106 -0
  88. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -0
  89. package/src/engine/graphics3/MeshSystem.js +647 -0
  90. package/src/engine/graphics3/MeshSystem3.d.ts +6 -102
  91. package/src/engine/graphics3/MeshSystem3.d.ts.map +1 -1
  92. package/src/engine/graphics3/MeshSystem3.js +6 -642
  93. package/src/engine/graphics3/ParticipatingMedia.d.ts +2 -2
  94. package/src/engine/graphics3/ParticipatingMedia.js +203 -203
  95. package/src/engine/graphics3/ParticipatingMediaSystem.d.ts +87 -0
  96. package/src/engine/graphics3/ParticipatingMediaSystem.d.ts.map +1 -0
  97. package/src/engine/graphics3/ParticipatingMediaSystem.js +184 -0
  98. package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts +6 -83
  99. package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts.map +1 -1
  100. package/src/engine/graphics3/ParticipatingMediaSystem3.js +6 -180
  101. package/src/engine/graphics3/ParticleEmitterSystem.d.ts +76 -0
  102. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -0
  103. package/src/engine/graphics3/ParticleEmitterSystem.js +289 -0
  104. package/src/engine/graphics3/ParticleEmitterSystem3.d.ts +6 -72
  105. package/src/engine/graphics3/ParticleEmitterSystem3.d.ts.map +1 -1
  106. package/src/engine/graphics3/ParticleEmitterSystem3.js +6 -285
  107. package/src/engine/graphics3/PathDisplaySystem.d.ts +70 -0
  108. package/src/engine/graphics3/PathDisplaySystem.d.ts.map +1 -0
  109. package/src/engine/graphics3/PathDisplaySystem.js +309 -0
  110. package/src/engine/graphics3/PathDisplaySystem3.d.ts +6 -66
  111. package/src/engine/graphics3/PathDisplaySystem3.d.ts.map +1 -1
  112. package/src/engine/graphics3/PathDisplaySystem3.js +6 -305
  113. package/src/engine/graphics3/Pickable.d.ts +1 -1
  114. package/src/engine/graphics3/Pickable.js +65 -65
  115. package/src/engine/graphics3/PickingQuery.d.ts +1 -1
  116. package/src/engine/graphics3/PickingQuery.js +48 -48
  117. package/src/engine/graphics3/PickingSystem.d.ts +67 -0
  118. package/src/engine/graphics3/PickingSystem.d.ts.map +1 -0
  119. package/src/engine/graphics3/PickingSystem.js +273 -0
  120. package/src/engine/graphics3/PickingSystem3.d.ts +6 -63
  121. package/src/engine/graphics3/PickingSystem3.d.ts.map +1 -1
  122. package/src/engine/graphics3/PickingSystem3.js +6 -269
  123. package/src/engine/graphics3/ShadedGeometrySystem.d.ts +47 -0
  124. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -0
  125. package/src/engine/graphics3/ShadedGeometrySystem.js +169 -0
  126. package/src/engine/graphics3/ShadedGeometrySystem3.d.ts +6 -43
  127. package/src/engine/graphics3/ShadedGeometrySystem3.d.ts.map +1 -1
  128. package/src/engine/graphics3/ShadedGeometrySystem3.js +6 -165
  129. package/src/engine/graphics3/TerrainSystem.d.ts +80 -0
  130. package/src/engine/graphics3/TerrainSystem.d.ts.map +1 -0
  131. package/src/engine/graphics3/TerrainSystem.js +566 -0
  132. package/src/engine/graphics3/TerrainSystem3.d.ts +6 -76
  133. package/src/engine/graphics3/TerrainSystem3.d.ts.map +1 -1
  134. package/src/engine/graphics3/TerrainSystem3.js +6 -562
  135. package/src/engine/graphics3/TooltipComponentSystem.d.ts +65 -0
  136. package/src/engine/graphics3/TooltipComponentSystem.d.ts.map +1 -0
  137. package/src/engine/graphics3/TooltipComponentSystem.js +328 -0
  138. package/src/engine/graphics3/TooltipComponentSystem3.d.ts +6 -61
  139. package/src/engine/graphics3/TooltipComponentSystem3.d.ts.map +1 -1
  140. package/src/engine/graphics3/TooltipComponentSystem3.js +6 -324
  141. package/src/engine/graphics3/Trail3DSystem.d.ts +73 -0
  142. package/src/engine/graphics3/Trail3DSystem.d.ts.map +1 -0
  143. package/src/engine/graphics3/Trail3DSystem.js +221 -0
  144. package/src/engine/graphics3/Trail3DSystem3.d.ts +6 -69
  145. package/src/engine/graphics3/Trail3DSystem3.d.ts.map +1 -1
  146. package/src/engine/graphics3/Trail3DSystem3.js +6 -217
  147. package/src/engine/graphics3/VolumetricLightMap.d.ts +2 -2
  148. package/src/engine/graphics3/VolumetricLightMap.js +2 -2
  149. package/src/engine/graphics3/VolumetricLightMapSystem.d.ts +98 -0
  150. package/src/engine/graphics3/VolumetricLightMapSystem.d.ts.map +1 -0
  151. package/src/engine/graphics3/VolumetricLightMapSystem.js +261 -0
  152. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts +6 -94
  153. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts.map +1 -1
  154. package/src/engine/graphics3/VolumetricLightMapSystem3.js +10 -261
  155. package/src/engine/graphics3/WaterSystem.d.ts +59 -0
  156. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -0
  157. package/src/engine/graphics3/WaterSystem.js +293 -0
  158. package/src/engine/graphics3/WaterSystem3.d.ts +6 -55
  159. package/src/engine/graphics3/WaterSystem3.d.ts.map +1 -1
  160. package/src/engine/graphics3/WaterSystem3.js +6 -289
  161. package/src/engine/graphics3/animation/AnimationGraphController.d.ts +1 -1
  162. package/src/engine/graphics3/animation/AnimationGraphController.js +518 -518
  163. package/src/engine/graphics3/animation/ClipListPlayer.d.ts +1 -1
  164. package/src/engine/graphics3/animation/ClipListPlayer.js +263 -263
  165. package/src/engine/graphics3/animation/ClipPlayback.d.ts +2 -2
  166. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  167. package/src/engine/graphics3/decal/graph_build_decal_clusters.d.ts.map +1 -1
  168. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +6 -8
  169. package/src/engine/graphics3/decal/shader_cluster_assign_decals.d.ts +0 -7
  170. package/src/engine/graphics3/decal/shader_cluster_assign_decals.d.ts.map +1 -1
  171. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +1 -1
  172. package/src/engine/graphics3/decal/shader_cull_decals_frustum.d.ts +0 -4
  173. package/src/engine/graphics3/decal/shader_cull_decals_frustum.d.ts.map +1 -1
  174. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +1 -1
  175. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts +2 -2
  176. package/src/engine/graphics3/highlight/pack_highlight_table.d.ts.map +1 -1
  177. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  178. package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts +1 -1
  179. package/src/engine/graphics3/pose/query_entity_node_world_pose.js +101 -101
  180. package/src/engine/graphics3/preview/make_model_thumbnail.d.ts +3 -3
  181. package/src/engine/graphics3/preview/make_model_thumbnail.d.ts.map +1 -1
  182. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  183. package/src/engine/graphics3/shade_camera_projection_ray.d.ts +1 -1
  184. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  185. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +1 -1
  186. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  187. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.d.ts.map +1 -1
  188. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.js +0 -1
  189. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.d.ts.map +1 -1
  190. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.js +0 -1
  191. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.d.ts.map +1 -1
  192. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.js +133 -134
  193. package/src/shade/playground/skinned_blas_refit/verify_leaf_triangles.js +202 -202
  194. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.js +249 -249
  195. package/src/shade/renderer/STATIC_GRAPHICS_ENGINE_ASSETS.js +1 -1
  196. package/src/shade/renderer/animation/pose/PosePlayback.d.ts +2 -1
  197. package/src/shade/renderer/animation/pose/PosePlayback.d.ts.map +1 -1
  198. package/src/shade/renderer/animation/pose/PosePlayback.js +2 -1
  199. package/src/shade/renderer/animation/shader_animation_apply.d.ts.map +1 -1
  200. package/src/shade/renderer/animation/shader_animation_apply.js +128 -129
  201. package/src/shade/renderer/animation/shader_animation_pose_resolve.d.ts.map +1 -1
  202. package/src/shade/renderer/animation/shader_animation_pose_resolve.js +176 -177
  203. package/src/shade/renderer/animation/shader_animation_tick.d.ts.map +1 -1
  204. package/src/shade/renderer/animation/shader_animation_tick.js +125 -126
  205. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.d.ts.map +1 -1
  206. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.js +98 -99
  207. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
  208. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.js +212 -213
  209. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.d.ts.map +1 -1
  210. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.js +120 -121
  211. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.d.ts.map +1 -1
  212. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.js +75 -76
  213. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.d.ts.map +1 -1
  214. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.js +371 -372
  215. package/src/shade/renderer/animation/skinning/shader_skin_matrix_prep.d.ts.map +1 -1
  216. package/src/shade/renderer/animation/skinning/shader_skin_matrix_prep.js +104 -105
  217. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
  218. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +0 -1
  219. package/src/shade/renderer/buffer/graph_inspect_gpu_buffer.js +136 -136
  220. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  221. package/src/shade/renderer/buffer/table/GPUDatabase.js +2 -3
  222. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +319 -319
  223. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.d.ts.map +1 -1
  224. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.js +41 -42
  225. package/src/shade/renderer/buffer/util/shader_buffer_intra_copy_regions.d.ts.map +1 -1
  226. package/src/shade/renderer/buffer/util/shader_buffer_intra_copy_regions.js +83 -84
  227. package/src/shade/renderer/dynamic/shader_dynamic_mesh.d.ts.map +1 -1
  228. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +177 -170
  229. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  230. package/src/shade/renderer/geometry/bvh/record_blas_refit.js +4 -4
  231. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.d.ts.map +1 -1
  232. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.js +121 -122
  233. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +774 -774
  234. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts +1 -1
  235. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts.map +1 -1
  236. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.js +69 -70
  237. package/src/shade/renderer/global_illumination/lpv/placement/graph_generate_probe_locations.js +367 -367
  238. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lvp_atlas_to_sh.js +184 -184
  239. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +1115 -1115
  240. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.js +217 -217
  241. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.d.ts.map +1 -1
  242. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.js +142 -143
  243. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.d.ts.map +1 -1
  244. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.js +0 -1
  245. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.d.ts.map +1 -1
  246. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.js +140 -141
  247. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.d.ts.map +1 -1
  248. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.js +0 -1
  249. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.d.ts.map +1 -1
  250. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.js +0 -1
  251. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.d.ts.map +1 -1
  252. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.js +191 -198
  253. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_frustum.d.ts +0 -6
  254. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_frustum.d.ts.map +1 -1
  255. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_frustum.js +120 -120
  256. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_hzb.d.ts +0 -4
  257. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_hzb.d.ts.map +1 -1
  258. package/src/shade/renderer/light/cluster/cull/shader_cull_lights_hzb.js +112 -112
  259. package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
  260. package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +4 -7
  261. package/src/shade/renderer/light/cluster/shader_cluster_assign_lights.d.ts +0 -6
  262. package/src/shade/renderer/light/cluster/shader_cluster_assign_lights.d.ts.map +1 -1
  263. package/src/shade/renderer/light/cluster/shader_cluster_assign_lights.js +1 -1
  264. package/src/shade/renderer/lightmap/bake/raster/shader_lightmap_to_viz_buffer.js +222 -222
  265. package/src/shade/renderer/lightmap/bake/tile/shader_lightmap_tile_composit.js +81 -81
  266. package/src/shade/renderer/material/GPUMaterialContext.js +380 -380
  267. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  268. package/src/shade/renderer/object_property/shader_object_property_process.d.ts.map +1 -1
  269. package/src/shade/renderer/object_property/shader_object_property_process.js +83 -84
  270. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.d.ts.map +1 -1
  271. package/src/shade/renderer/object_property/shader_refresh_all_mesh_bounds.js +57 -58
  272. package/src/shade/renderer/particles/prototypeParticleSystem.js +2 -3
  273. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  274. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +117 -118
  275. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  276. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +77 -79
  277. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/shaders/shader_particle_render.js +190 -183
  279. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  280. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +99 -100
  281. package/src/shade/renderer/particles/sort/shader_particle_sort.d.ts.map +1 -1
  282. package/src/shade/renderer/particles/sort/shader_particle_sort.js +158 -161
  283. package/src/shade/renderer/path_tracer/graph_path_trace_scene.js +184 -184
  284. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.js +119 -119
  285. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +3 -4
  286. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.d.ts.map +1 -1
  287. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +266 -267
  288. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.d.ts.map +1 -1
  289. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +270 -271
  290. package/src/shade/renderer/postprocess/nss/shader_nss_concat.d.ts.map +1 -1
  291. package/src/shade/renderer/postprocess/nss/shader_nss_concat.js +68 -69
  292. package/src/shade/renderer/postprocess/nss/shader_nss_conv_layer.d.ts.map +1 -1
  293. package/src/shade/renderer/postprocess/nss/shader_nss_conv_layer.js +225 -226
  294. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.d.ts.map +1 -1
  295. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.js +49 -50
  296. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.d.ts.map +1 -1
  297. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.js +291 -292
  298. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.d.ts.map +1 -1
  299. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.js +230 -231
  300. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts +16 -2
  301. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts.map +1 -1
  302. package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.js +12 -12
  303. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +2 -2
  304. package/src/shade/renderer/rasterize/bucket/shader_count_meshlets_by_material.d.ts.map +1 -1
  305. package/src/shade/renderer/rasterize/bucket/shader_count_meshlets_by_material.js +68 -69
  306. package/src/shade/renderer/rasterize/bucket/shader_make_meshlet_indirect_draw_commands_by_material.d.ts.map +1 -1
  307. package/src/shade/renderer/rasterize/bucket/shader_make_meshlet_indirect_draw_commands_by_material.js +59 -60
  308. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  309. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +0 -1
  310. package/src/shade/renderer/rasterize/bucket/shader_sort_meshlets_by_material.d.ts.map +1 -1
  311. package/src/shade/renderer/rasterize/bucket/shader_sort_meshlets_by_material.js +64 -65
  312. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.d.ts.map +1 -1
  313. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.js +168 -169
  314. package/src/shade/renderer/rasterize/compute/graph_mesh_sphere_cull.d.ts.map +1 -1
  315. package/src/shade/renderer/rasterize/compute/graph_mesh_sphere_cull.js +160 -161
  316. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  317. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +109 -110
  318. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  319. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +115 -116
  320. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_classify_bucket.d.ts.map +1 -1
  321. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_classify_bucket.js +139 -140
  322. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_count_by_rasterization_bucket.d.ts.map +1 -1
  323. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_count_by_rasterization_bucket.js +91 -92
  324. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.d.ts.map +1 -1
  325. package/src/shade/renderer/rasterize/expand/bucket/mesh/shader_instances_rasterization_bucket_sort.js +88 -89
  326. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.d.ts.map +1 -1
  327. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.js +90 -91
  328. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  329. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +65 -66
  330. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
  331. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +89 -90
  332. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.d.ts.map +1 -1
  333. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.js +169 -170
  334. package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.d.ts.map +1 -1
  335. package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.js +89 -90
  336. package/src/shade/renderer/rasterize/expand/mesh/shader_meshes_to_meshlet_counts.d.ts.map +1 -1
  337. package/src/shade/renderer/rasterize/expand/mesh/shader_meshes_to_meshlet_counts.js +61 -62
  338. package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.d.ts.map +1 -1
  339. package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.js +153 -154
  340. package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.js +144 -144
  341. package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_pass_descriptor.js +88 -88
  342. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +217 -217
  343. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +179 -179
  344. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  345. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.js +194 -194
  346. package/src/shade/renderer/rasterize/native/viz/viz_rasterization_opaque_pass_descriptor.js +76 -76
  347. package/src/shade/renderer/restir/di/shader_restir_di_denoise.d.ts.map +1 -1
  348. package/src/shade/renderer/restir/di/shader_restir_di_denoise.js +136 -137
  349. package/src/shade/renderer/restir/di/shader_restir_di_resample.d.ts.map +1 -1
  350. package/src/shade/renderer/restir/di/shader_restir_di_resample.js +129 -130
  351. package/src/shade/renderer/restir/di/shader_restir_di_resolve.d.ts.map +1 -1
  352. package/src/shade/renderer/restir/di/shader_restir_di_resolve.js +99 -100
  353. package/src/shade/renderer/restir/di/shader_restir_di_spatial.d.ts.map +1 -1
  354. package/src/shade/renderer/restir/di/shader_restir_di_spatial.js +116 -117
  355. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.js +383 -383
  356. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy_naive.js +123 -123
  357. package/src/shade/renderer/scene/hierarchy/shader_nodes_record_input_positions.d.ts.map +1 -1
  358. package/src/shade/renderer/scene/hierarchy/shader_nodes_record_input_positions.js +64 -65
  359. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy.d.ts.map +1 -1
  360. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy.js +372 -373
  361. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy_naive.d.ts.map +1 -1
  362. package/src/shade/renderer/scene/hierarchy/shader_nodes_update_transform_hierarchy_naive.js +111 -112
  363. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
  364. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.js +0 -1
  365. package/src/shade/renderer/shader/ComputeShader.d.ts +24 -3
  366. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  367. package/src/shade/renderer/shader/ComputeShader.js +350 -296
  368. package/src/shade/renderer/shader/ImageShader.js +217 -217
  369. package/src/shade/renderer/shader/ShaderDescriptor.d.ts +5 -0
  370. package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
  371. package/src/shade/renderer/shader/ShaderDescriptor.js +20 -0
  372. package/src/shade/renderer/shader/util/compute_infer_workgroup_size_from_source.d.ts +47 -0
  373. package/src/shade/renderer/shader/util/compute_infer_workgroup_size_from_source.d.ts.map +1 -0
  374. package/src/shade/renderer/shader/util/compute_infer_workgroup_size_from_source.js +448 -0
  375. package/src/shade/renderer/shadow/map/csm/graph_csm_setup_cascades.js +3 -3
  376. package/src/shade/renderer/shadow/ray/staged/gen/graph_gen_rays.js +3 -3
  377. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.js +2 -2
  378. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.js +4 -4
  379. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.d.ts.map +1 -1
  380. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +231 -232
  381. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +1 -1
  382. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  383. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +12 -15
  384. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +3 -2
  385. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
  386. package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +3 -2
  387. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.d.ts +0 -2
  388. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.d.ts.map +1 -1
  389. package/src/shade/renderer/volumetrics/taa/shader_volumetrics_taa.js +17 -6
  390. package/src/shade/renderer/water/shader_water.js +246 -246
  391. package/src/shade/vitest.setup.mjs +1 -1
  392. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +291 -291
  393. package/src/view/minimap/Minimap.d.ts +2 -2
  394. package/src/view/minimap/Minimap.d.ts.map +1 -1
  395. package/src/view/minimap/Minimap.js +225 -225
  396. package/src/view/minimap/dom/MinimapCameraView.d.ts +3 -3
  397. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  398. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  399. package/src/shade/renderer/shadow/map/csm/shader/shader_setup_cascades.d.ts +0 -3
  400. package/src/shade/renderer/shadow/map/csm/shader/shader_setup_cascades.d.ts.map +0 -1
  401. package/src/shade/renderer/shadow/map/csm/shader/shader_setup_cascades.js +0 -20
@@ -1,372 +1,371 @@
1
- import { mat4x4f, u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
- import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
3
- import {
4
- MESHLET_METADATA_FLAGS
5
- } from "../../geometry/meshlet/encoding/MESHLET_METADATA_FLAGS.js";
6
- import {
7
- MESHLET_METADATA_STRUCT
8
- } from "../../geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
9
- import { chunk_scene_database_access } from "../../scene/chunk_scene_database_access.js";
10
- import { chunk_mat4_inverse } from "../../shader/chunk/geometry/mat4/chunk_mat4_inverse.js";
11
- import {
12
- chunk_meshlet_compute_attribute_section_offset
13
- } from "../../shader/chunk/geometry/meshlet/chunk_meshlet_compute_attribute_section_offset.js";
14
- import {
15
- chunk_decode_vertex_normal
16
- } from "../../shader/chunk/geometry/normal/chunk_decode_vertex_normal.js";
17
- import {
18
- chunk_encode_vertex_normal
19
- } from "../../shader/chunk/geometry/normal/chunk_encode_vertex_normal.js";
20
- import {
21
- chunk_decode_vertex_tangent
22
- } from "../../shader/chunk/geometry/normal/tangent/chunk_decode_vertex_tangent.js";
23
- import {
24
- chunk_encode_vertex_tangent
25
- } from "../../shader/chunk/geometry/normal/tangent/chunk_encode_vertex_tangent.js";
26
- import {
27
- chunk_dual_quat_transform_direction
28
- } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_direction.js";
29
- import {
30
- chunk_dual_quat_transform_point
31
- } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js";
32
- import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
33
- import { ComputeShader } from "../../shader/ComputeShader.js";
34
- import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
35
- import { chunk_skin_blend_mesh_local } from "./chunk_skin_blend_mesh_local.js";
36
- import { SKINNING_BINDING_STRUCT } from "./SKINNING_BINDING_STRUCT.js";
37
-
38
- /**
39
- * Workgroup size — also the per-meshlet vertex stride. Each thread
40
- * handles vertex `local_id`, then loops by `WORKGROUP_SIZE` for
41
- * meshlets larger than the workgroup.
42
- */
43
- export const SKINNING_WORKGROUP_SIZE = 64;
44
-
45
- const resources = new ShaderResourceSetDescriptor();
46
- resources.createGroup()
47
- // First workgroup of the current sub-dispatch. WebGPU caps
48
- // `dispatchWorkgroups` along each dimension at the device's
49
- // `maxComputeWorkgroupsPerDimension` (65535 minimum per the
50
- // spec, often higher in practice), so the host paginates large
51
- // totals into multiple dispatches and increments this offset
52
- // between them. The shader adds it to `wg_id.x` before indexing
53
- // `dispatch_lookup`, so the per-pair mapping stays a flat array
54
- // regardless of how many sub-dispatches it gets split into.
55
- .addUniform('dispatch_offset', u32)
56
- .addStorageBuffer('meshlet_data', WebGPUArray.from(u32), true)
57
- .addStorageBuffer('meshlet_metadata', WebGPUArray.from(MESHLET_METADATA_STRUCT), false)
58
- .addStorageBuffer('skin_matrices', WebGPUArray.from(mat4x4f), false)
59
- .addStorageBuffer('prev_skin_matrices', WebGPUArray.from(mat4x4f), false)
60
- .addStorageBuffer('skinning_bindings', WebGPUArray.from(SKINNING_BINDING_STRUCT), false)
61
- .addStorageBuffer('dispatch_lookup', WebGPUArray.from(u32), false)
62
- // Per-meshlet base u32-word offset into the side-buffer that
63
- // holds previous-frame mesh-local positions. Sentinel
64
- // `0xFFFFFFFF` for non-clone meshlets — those skip the
65
- // prev-position write entirely, since the velocity post-pass
66
- // will fall back to per-object math for them.
67
- .addStorageBuffer('prev_position_offsets', WebGPUArray.from(u32), false)
68
- .addStorageBuffer('prev_positions', WebGPUArray.from(u32), true)
69
- // Scene-graph state — read-only. We need the SkinnedMesh's
70
- // `transforms` row to source the live `mesh.transform_global`
71
- // for the post-DQ position cancellation (see commentary in main).
72
- .addStorageBuffer('scene_database', WebGPUArray.from(u32), false);
73
-
74
- // One workgroup per (binding, meshlet) pair. The host builds
75
- // `dispatch_lookup` as a flat (binding_index, meshlet_local) pair
76
- // table and dispatches one workgroup per pair, so workgroup_id.x
77
- // directly indexes the table.
78
- //
79
- // Skinning math is dual-quaternion: each of the 4 per-vertex joints
80
- // contributes a DQ (built from its skin matrix), the four DQs blend
81
- // by weight (with antipodal correction), and the result transforms
82
- // position via the full screw motion and normal/tangent via just the
83
- // rotation part. DQ blending avoids the candy-wrapper artifacts of
84
- // linear matrix blending and lets normals + tangents transform with
85
- // the same blended frame, no matrix inversion or gimbal-style
86
- // interpolation issues.
87
- //
88
- // Per-attribute compression handling: Position is always
89
- // uncompressed (one entry per vertex, vertex_count*3 words). For
90
- // Normal and Tangent, the meshlet flags say whether they were
91
- // collapsed to a single shared entry across all vertices. Per-vertex
92
- // skinning into a shared slot would race, so the shader skips the
93
- // write for any attribute where the flag is set — the cloned slot
94
- // keeps the source's value and lighting on those vertices uses an
95
- // undeformed direction. For typical character meshes
96
- // normals/tangents vary per vertex and so don't get the all-equal
97
- // collapse, so this skip rarely engages in practice.
98
- //
99
- // SkinningJoints + SkinningWeights are read-only here. They CAN be
100
- // compressed — a meshlet where every vertex shares the same joint
101
- // palette (e.g. simple test meshes that bind every vertex to the
102
- // same two bones) collapses Joints to one shared u32-pair, and the
103
- // shader then has to broadcast that single entry across all vertex
104
- // reads instead of striding by 2 words per vertex. Same logic for
105
- // Weights (less common in practice — weights usually vary per
106
- // vertex even on simple meshes — but the flag handling is free).
107
- const body = CodeChunk.from(
108
- //language=WGSL
109
- `
110
- @compute @workgroup_size(${SKINNING_WORKGROUP_SIZE},1,1)
111
- fn main(
112
- @builtin(workgroup_id) wg_id: vec3<u32>,
113
- @builtin(local_invocation_index) local_id: u32,
114
- ) {
115
- // dispatch_offset lets the host paginate large dispatches —
116
- // WebGPU's per-dimension cap is 65535 workgroups, but a busy
117
- // scene's (binding, meshlet) pair total can exceed that.
118
- let pair_index = wg_id.x + dispatch_offset;
119
- let lookup_base = pair_index * 2u;
120
- let binding_index = dispatch_lookup[lookup_base];
121
- let meshlet_local = dispatch_lookup[lookup_base + 1u];
122
-
123
- let binding = skinning_bindings[binding_index];
124
-
125
- // The glTF skinning formula's leftmost factor is
126
- // inverse(meshNode.worldMatrix), which lands the per-vertex
127
- // output in mesh-local space ready for the rasterizer's
128
- // mesh.world re-application (see
129
- // chunk_mesh_resolve_vertex_position_world.js). It goes into
130
- // each *matrix*, not onto the blended position — see
131
- // chunk_skin_blend_mesh_local for why the difference is the
132
- // whole ball game. Both positions and directions therefore come
133
- // out of the blend already in mesh-local space, and the
134
- // rasterizer's own transforms take them to world.
135
- //
136
- // mesh_instance_index is the row of the SkinnedMesh in the
137
- // meshes table; mesh.node is its transforms-table row.
138
- // Workgroup-uniform (one binding per workgroup), so the load +
139
- // inverse amortize across every vertex this workgroup
140
- // processes.
141
- let mesh = scene_read_mesh(&scene_database, binding.mesh_instance_index);
142
- let mesh_node = scene_read_node(&scene_database, mesh.node);
143
- let inv_mesh_world = mat4_inverse(mesh_node.global);
144
-
145
- let source_meshlet = meshlet_metadata[binding.source_meshlet_metadata_offset + meshlet_local];
146
- let clone_meshlet = meshlet_metadata[binding.clone_meshlet_metadata_offset + meshlet_local];
147
-
148
- let vertex_count = source_meshlet.vertex_count;
149
-
150
- // Both source and clone share an attribute layout (the clone is a
151
- // memcpy of the source's bytes with addresses patched), so attribute
152
- // offsets relative to each meshlet's attribute section are identical.
153
- let source_attr_section = meshlet_compute_attribute_section_offset(source_meshlet);
154
- let clone_attr_section = meshlet_compute_attribute_section_offset(clone_meshlet);
155
-
156
- // Walk the per-attribute layout, capturing the offset of each
157
- // attribute we'll need. Order matches ENCODED_ORDERED_ATTRIBUTES:
158
- // Position, Normal, Tangent, Color, UV0, UV1, SkinningJoints,
159
- // SkinningWeights. Conditional skips for the compressible ones —
160
- // when collapsed, the slot is a single shared entry rather than
161
- // vertex_count entries.
162
- let position_offset = 0u;
163
- var attr_offset: u32 = vertex_count * 3u;
164
-
165
- let normal_offset = attr_offset;
166
- let normal_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_NORMAL}u) != 0u;
167
- attr_offset = attr_offset + select(vertex_count, 1u, normal_compressed);
168
-
169
- let tangent_offset = attr_offset;
170
- let tangent_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TANGENT}u) != 0u;
171
- attr_offset = attr_offset + select(vertex_count, 1u, tangent_compressed);
172
-
173
- let color_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_COLOR}u) != 0u;
174
- attr_offset = attr_offset + select(vertex_count, 1u, color_compressed);
175
-
176
- let uv0_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_0}u) != 0u;
177
- attr_offset = attr_offset + select(vertex_count * 2u, 2u, uv0_compressed);
178
-
179
- let uv1_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_1}u) != 0u;
180
- attr_offset = attr_offset + select(vertex_count, 1u, uv1_compressed);
181
-
182
- let joints_offset = attr_offset;
183
- let joints_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_JOINTS}u) != 0u;
184
- attr_offset = attr_offset + select(vertex_count * 2u, 2u, joints_compressed);
185
-
186
- let weights_offset = attr_offset;
187
- let weights_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_WEIGHTS}u) != 0u;
188
-
189
- let skin_base = binding.skin_matrix_offset;
190
-
191
- // Resolve the clone meshlet's per-meshlet base offset in the
192
- // prev_positions side-buffer (or sentinel if this meshlet
193
- // somehow isn't a clone — shouldn't happen in practice since
194
- // the skinning shader only runs on (clone, skinned) pairs, but
195
- // the check costs one load and one branch). Lifted out of the
196
- // vertex loop because it's per-meshlet, not per-vertex.
197
- let clone_meshlet_abs = binding.clone_meshlet_metadata_offset + meshlet_local;
198
- let prev_pos_meshlet_base = prev_position_offsets[clone_meshlet_abs];
199
- let has_prev_positions = prev_pos_meshlet_base != 0xFFFFFFFFu;
200
-
201
- // Stride loop — one thread per WORKGROUP_SIZE-stride'd vertex slot.
202
- // Most meshlets fit in a single iteration (vertex_count <=
203
- // WORKGROUP_SIZE); larger ones cycle the workgroup over them.
204
- var v: u32 = local_id;
205
- loop {
206
- if v >= vertex_count { break; }
207
-
208
- // Joint indices: vec4<u16> packed into 2 u32s. When the
209
- // attribute is compressed (every vertex of this meshlet
210
- // shares the same palette), there's one shared pair at
211
- // joints_offset rather than per-vertex strides; broadcast
212
- // that single entry to every thread.
213
- let joints_local_offset = select(v * 2u, 0u, joints_compressed);
214
- let joints_addr = source_attr_section + joints_offset + joints_local_offset;
215
- let raw_j0 = meshlet_data[joints_addr];
216
- let raw_j1 = meshlet_data[joints_addr + 1u];
217
- let j0 = raw_j0 & 0xFFFFu;
218
- let j1 = (raw_j0 >> 16u) & 0xFFFFu;
219
- let j2 = raw_j1 & 0xFFFFu;
220
- let j3 = (raw_j1 >> 16u) & 0xFFFFu;
221
-
222
- // Joint weights: vec4<unorm8> packed into 1 u32. Half the
223
- // bandwidth of the previous f16×4 layout, sufficient
224
- // precision for typical character skinning (step size
225
- // ~0.4%, well under any visible artefact threshold).
226
- // unpack4x8unorm returns vec4<f32> in [0, 1] with
227
- // components matching byte 0..3 of the packed u32.
228
- let weights_local_offset = select(v, 0u, weights_compressed);
229
- let weights_addr = source_attr_section + weights_offset + weights_local_offset;
230
- let weights = unpack4x8unorm(meshlet_data[weights_addr]);
231
-
232
- // Build per-joint dual quaternions from the joint matrices, then
233
- // blend with antipodal correction. The result represents the
234
- // weighted-average screw motion the four matrices encode.
235
- let dq_blend = skin_blend_mesh_local(
236
- inv_mesh_world,
237
- skin_matrices[skin_base + j0],
238
- skin_matrices[skin_base + j1],
239
- skin_matrices[skin_base + j2],
240
- skin_matrices[skin_base + j3],
241
- weights,
242
- );
243
-
244
- // Position: full screw motion (rotation + translation).
245
- let pos_addr = source_attr_section + position_offset + v * 3u;
246
- let p = vec3f(
247
- bitcast<f32>(meshlet_data[pos_addr]),
248
- bitcast<f32>(meshlet_data[pos_addr + 1u]),
249
- bitcast<f32>(meshlet_data[pos_addr + 2u]),
250
- );
251
- // Position: full screw motion, already mesh-local — the
252
- // rasterizer reapplies mesh.world to every mesh's vertices,
253
- // skinned or not.
254
- let p_skinned = dual_quat_transform_point(dq_blend, p);
255
-
256
- let clone_pos_addr = clone_attr_section + position_offset + v * 3u;
257
- meshlet_data[clone_pos_addr] = bitcast<u32>(p_skinned.x);
258
- meshlet_data[clone_pos_addr + 1u] = bitcast<u32>(p_skinned.y);
259
- meshlet_data[clone_pos_addr + 2u] = bitcast<u32>(p_skinned.z);
260
-
261
- // Previous-frame mesh-local position: same dual-quat math,
262
- // but driven by prev_skin_matrices instead of skin_matrices.
263
- // Written to the side-buffer at the per-meshlet base
264
- // offset + v * 3 u32 words. The velocity post-pass reads
265
- // these to interpolate per-vertex skin deformation across
266
- // the rasterized triangle (vs. just the rigid mesh.global
267
- // → mesh.prev_global delta that Stage 1's path captures).
268
- //
269
- // Skipping the write when there's no slot for this meshlet
270
- // (non-clone — shouldn't be hit by this shader's dispatch
271
- // set, but the guard is cheap). The dual-quat build is
272
- // also skipped to save the per-joint work.
273
- if has_prev_positions {
274
- // Mesh-local for the same reason the current-frame
275
- // position is — the velocity pass reads these through
276
- // the same rasterizer transform, so the space they live
277
- // in has to match.
278
- let prev_dq_blend = skin_blend_mesh_local(
279
- inv_mesh_world,
280
- prev_skin_matrices[skin_base + j0],
281
- prev_skin_matrices[skin_base + j1],
282
- prev_skin_matrices[skin_base + j2],
283
- prev_skin_matrices[skin_base + j3],
284
- weights,
285
- );
286
-
287
- let p_prev_skinned = dual_quat_transform_point(prev_dq_blend, p);
288
-
289
- let prev_addr = prev_pos_meshlet_base + v * 3u;
290
- prev_positions[prev_addr] = bitcast<u32>(p_prev_skinned.x);
291
- prev_positions[prev_addr + 1u] = bitcast<u32>(p_prev_skinned.y);
292
- prev_positions[prev_addr + 2u] = bitcast<u32>(p_prev_skinned.z);
293
- }
294
-
295
- // Normal: rotation only. Skip when the source compressed it
296
- // down to a single shared slot — per-vertex writes to that
297
- // slot would race, and the rotated direction would be
298
- // mostly-meaningless anyway since every vertex would compute
299
- // a different one.
300
- //
301
- // Mesh-local like the position, and left there: the
302
- // rasterizer applies the inverse-transpose of mesh.world's
303
- // 3x3 to it, which is the glTF normal transform and takes a
304
- // mesh-local direction to world (see fragment_gbuffer.js).
305
- if !normal_compressed {
306
- let n_raw_addr = source_attr_section + normal_offset + v;
307
- let n = decode_vertex_normal(meshlet_data[n_raw_addr]);
308
- let n_skinned = normalize(dual_quat_transform_direction(dq_blend, n));
309
-
310
- let clone_n_addr = clone_attr_section + normal_offset + v;
311
- meshlet_data[clone_n_addr] = encode_vertex_normal(n_skinned);
312
- }
313
-
314
- // Tangent: rotation only on the xyz; w (handedness) is a
315
- // topological property, copied through unchanged.
316
- if !tangent_compressed {
317
- let t_raw_addr = source_attr_section + tangent_offset + v;
318
- let t = decode_vertex_tangent(meshlet_data[t_raw_addr]);
319
- let t_skinned_dir = normalize(dual_quat_transform_direction(dq_blend, t.xyz));
320
-
321
- let clone_t_addr = clone_attr_section + tangent_offset + v;
322
- meshlet_data[clone_t_addr] = encode_vertex_tangent(vec4f(t_skinned_dir, t.w));
323
- }
324
-
325
- v = v + ${SKINNING_WORKGROUP_SIZE}u;
326
- }
327
- }
328
- `, [
329
- MESHLET_METADATA_STRUCT.declaration_chunk,
330
- SKINNING_BINDING_STRUCT.declaration_chunk,
331
- chunk_meshlet_compute_attribute_section_offset,
332
- chunk_skin_blend_mesh_local,
333
- chunk_dual_quat_transform_point,
334
- chunk_dual_quat_transform_direction,
335
- chunk_decode_vertex_normal,
336
- chunk_encode_vertex_normal,
337
- chunk_decode_vertex_tangent,
338
- chunk_encode_vertex_tangent,
339
- chunk_scene_database_access,
340
- chunk_mat4_inverse,
341
- ]
342
- );
343
-
344
- /**
345
- * Compute shader that deforms cloned meshlet vertex regions in place
346
- * using the per-frame skin matrices the prep pass produced. One
347
- * workgroup per (binding, meshlet) pair, dispatched against a flat
348
- * `dispatch_lookup` table the host builds when bindings change.
349
- *
350
- * Skins position (full transform) plus normal and tangent (rotation
351
- * only) per vertex via dual-quaternion blending — the blended DQ
352
- * captures the four per-vertex joints' screw motions weighted by
353
- * the per-vertex `SkinningWeights`, and applies cleanly to both
354
- * positions and unit-direction attributes without the matrix-blend
355
- * candy-wrapper artifacts that would otherwise show up in the
356
- * normal-transformed lighting.
357
- *
358
- * Source bytes stay untouched — other instances pointed at the
359
- * source render normally. Per-meshlet bounds aren't refreshed by
360
- * this pass; meshlets that animate outside the source bounds may
361
- * cull incorrectly until a future bounds-refresh phase lands.
362
- *
363
- * Dispatch via `shader.dispatch({ group_count_x: total_meshlet_count, ... })`
364
- * where `total_meshlet_count` is the sum of `meshlet_count` across
365
- * every registered binding. Skip when `total_meshlet_count === 0`.
366
- */
367
- export const shader_meshlet_apply_skinning = ComputeShader.from({
368
- label: "Skinning/MeshletApplySkinning",
369
- resources,
370
- body,
371
- group_size: [SKINNING_WORKGROUP_SIZE, 1, 1],
372
- });
1
+ import { mat4x4f, u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
2
+ import { WebGPUArray } from "../../shader/type/WebGPUArray.js";
3
+ import {
4
+ MESHLET_METADATA_FLAGS
5
+ } from "../../geometry/meshlet/encoding/MESHLET_METADATA_FLAGS.js";
6
+ import {
7
+ MESHLET_METADATA_STRUCT
8
+ } from "../../geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
9
+ import { chunk_scene_database_access } from "../../scene/chunk_scene_database_access.js";
10
+ import { chunk_mat4_inverse } from "../../shader/chunk/geometry/mat4/chunk_mat4_inverse.js";
11
+ import {
12
+ chunk_meshlet_compute_attribute_section_offset
13
+ } from "../../shader/chunk/geometry/meshlet/chunk_meshlet_compute_attribute_section_offset.js";
14
+ import {
15
+ chunk_decode_vertex_normal
16
+ } from "../../shader/chunk/geometry/normal/chunk_decode_vertex_normal.js";
17
+ import {
18
+ chunk_encode_vertex_normal
19
+ } from "../../shader/chunk/geometry/normal/chunk_encode_vertex_normal.js";
20
+ import {
21
+ chunk_decode_vertex_tangent
22
+ } from "../../shader/chunk/geometry/normal/tangent/chunk_decode_vertex_tangent.js";
23
+ import {
24
+ chunk_encode_vertex_tangent
25
+ } from "../../shader/chunk/geometry/normal/tangent/chunk_encode_vertex_tangent.js";
26
+ import {
27
+ chunk_dual_quat_transform_direction
28
+ } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_direction.js";
29
+ import {
30
+ chunk_dual_quat_transform_point
31
+ } from "../../shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js";
32
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
33
+ import { ComputeShader } from "../../shader/ComputeShader.js";
34
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
35
+ import { chunk_skin_blend_mesh_local } from "./chunk_skin_blend_mesh_local.js";
36
+ import { SKINNING_BINDING_STRUCT } from "./SKINNING_BINDING_STRUCT.js";
37
+
38
+ /**
39
+ * Workgroup size — also the per-meshlet vertex stride. Each thread
40
+ * handles vertex `local_id`, then loops by `WORKGROUP_SIZE` for
41
+ * meshlets larger than the workgroup.
42
+ */
43
+ export const SKINNING_WORKGROUP_SIZE = 64;
44
+
45
+ const resources = new ShaderResourceSetDescriptor();
46
+ resources.createGroup()
47
+ // First workgroup of the current sub-dispatch. WebGPU caps
48
+ // `dispatchWorkgroups` along each dimension at the device's
49
+ // `maxComputeWorkgroupsPerDimension` (65535 minimum per the
50
+ // spec, often higher in practice), so the host paginates large
51
+ // totals into multiple dispatches and increments this offset
52
+ // between them. The shader adds it to `wg_id.x` before indexing
53
+ // `dispatch_lookup`, so the per-pair mapping stays a flat array
54
+ // regardless of how many sub-dispatches it gets split into.
55
+ .addUniform('dispatch_offset', u32)
56
+ .addStorageBuffer('meshlet_data', WebGPUArray.from(u32), true)
57
+ .addStorageBuffer('meshlet_metadata', WebGPUArray.from(MESHLET_METADATA_STRUCT), false)
58
+ .addStorageBuffer('skin_matrices', WebGPUArray.from(mat4x4f), false)
59
+ .addStorageBuffer('prev_skin_matrices', WebGPUArray.from(mat4x4f), false)
60
+ .addStorageBuffer('skinning_bindings', WebGPUArray.from(SKINNING_BINDING_STRUCT), false)
61
+ .addStorageBuffer('dispatch_lookup', WebGPUArray.from(u32), false)
62
+ // Per-meshlet base u32-word offset into the side-buffer that
63
+ // holds previous-frame mesh-local positions. Sentinel
64
+ // `0xFFFFFFFF` for non-clone meshlets — those skip the
65
+ // prev-position write entirely, since the velocity post-pass
66
+ // will fall back to per-object math for them.
67
+ .addStorageBuffer('prev_position_offsets', WebGPUArray.from(u32), false)
68
+ .addStorageBuffer('prev_positions', WebGPUArray.from(u32), true)
69
+ // Scene-graph state — read-only. We need the SkinnedMesh's
70
+ // `transforms` row to source the live `mesh.transform_global`
71
+ // for the post-DQ position cancellation (see commentary in main).
72
+ .addStorageBuffer('scene_database', WebGPUArray.from(u32), false);
73
+
74
+ // One workgroup per (binding, meshlet) pair. The host builds
75
+ // `dispatch_lookup` as a flat (binding_index, meshlet_local) pair
76
+ // table and dispatches one workgroup per pair, so workgroup_id.x
77
+ // directly indexes the table.
78
+ //
79
+ // Skinning math is dual-quaternion: each of the 4 per-vertex joints
80
+ // contributes a DQ (built from its skin matrix), the four DQs blend
81
+ // by weight (with antipodal correction), and the result transforms
82
+ // position via the full screw motion and normal/tangent via just the
83
+ // rotation part. DQ blending avoids the candy-wrapper artifacts of
84
+ // linear matrix blending and lets normals + tangents transform with
85
+ // the same blended frame, no matrix inversion or gimbal-style
86
+ // interpolation issues.
87
+ //
88
+ // Per-attribute compression handling: Position is always
89
+ // uncompressed (one entry per vertex, vertex_count*3 words). For
90
+ // Normal and Tangent, the meshlet flags say whether they were
91
+ // collapsed to a single shared entry across all vertices. Per-vertex
92
+ // skinning into a shared slot would race, so the shader skips the
93
+ // write for any attribute where the flag is set — the cloned slot
94
+ // keeps the source's value and lighting on those vertices uses an
95
+ // undeformed direction. For typical character meshes
96
+ // normals/tangents vary per vertex and so don't get the all-equal
97
+ // collapse, so this skip rarely engages in practice.
98
+ //
99
+ // SkinningJoints + SkinningWeights are read-only here. They CAN be
100
+ // compressed — a meshlet where every vertex shares the same joint
101
+ // palette (e.g. simple test meshes that bind every vertex to the
102
+ // same two bones) collapses Joints to one shared u32-pair, and the
103
+ // shader then has to broadcast that single entry across all vertex
104
+ // reads instead of striding by 2 words per vertex. Same logic for
105
+ // Weights (less common in practice — weights usually vary per
106
+ // vertex even on simple meshes — but the flag handling is free).
107
+ const body = CodeChunk.from(
108
+ //language=WGSL
109
+ `
110
+ @compute @workgroup_size(${SKINNING_WORKGROUP_SIZE},1,1)
111
+ fn main(
112
+ @builtin(workgroup_id) wg_id: vec3<u32>,
113
+ @builtin(local_invocation_index) local_id: u32,
114
+ ) {
115
+ // dispatch_offset lets the host paginate large dispatches —
116
+ // WebGPU's per-dimension cap is 65535 workgroups, but a busy
117
+ // scene's (binding, meshlet) pair total can exceed that.
118
+ let pair_index = wg_id.x + dispatch_offset;
119
+ let lookup_base = pair_index * 2u;
120
+ let binding_index = dispatch_lookup[lookup_base];
121
+ let meshlet_local = dispatch_lookup[lookup_base + 1u];
122
+
123
+ let binding = skinning_bindings[binding_index];
124
+
125
+ // The glTF skinning formula's leftmost factor is
126
+ // inverse(meshNode.worldMatrix), which lands the per-vertex
127
+ // output in mesh-local space ready for the rasterizer's
128
+ // mesh.world re-application (see
129
+ // chunk_mesh_resolve_vertex_position_world.js). It goes into
130
+ // each *matrix*, not onto the blended position — see
131
+ // chunk_skin_blend_mesh_local for why the difference is the
132
+ // whole ball game. Both positions and directions therefore come
133
+ // out of the blend already in mesh-local space, and the
134
+ // rasterizer's own transforms take them to world.
135
+ //
136
+ // mesh_instance_index is the row of the SkinnedMesh in the
137
+ // meshes table; mesh.node is its transforms-table row.
138
+ // Workgroup-uniform (one binding per workgroup), so the load +
139
+ // inverse amortize across every vertex this workgroup
140
+ // processes.
141
+ let mesh = scene_read_mesh(&scene_database, binding.mesh_instance_index);
142
+ let mesh_node = scene_read_node(&scene_database, mesh.node);
143
+ let inv_mesh_world = mat4_inverse(mesh_node.global);
144
+
145
+ let source_meshlet = meshlet_metadata[binding.source_meshlet_metadata_offset + meshlet_local];
146
+ let clone_meshlet = meshlet_metadata[binding.clone_meshlet_metadata_offset + meshlet_local];
147
+
148
+ let vertex_count = source_meshlet.vertex_count;
149
+
150
+ // Both source and clone share an attribute layout (the clone is a
151
+ // memcpy of the source's bytes with addresses patched), so attribute
152
+ // offsets relative to each meshlet's attribute section are identical.
153
+ let source_attr_section = meshlet_compute_attribute_section_offset(source_meshlet);
154
+ let clone_attr_section = meshlet_compute_attribute_section_offset(clone_meshlet);
155
+
156
+ // Walk the per-attribute layout, capturing the offset of each
157
+ // attribute we'll need. Order matches ENCODED_ORDERED_ATTRIBUTES:
158
+ // Position, Normal, Tangent, Color, UV0, UV1, SkinningJoints,
159
+ // SkinningWeights. Conditional skips for the compressible ones —
160
+ // when collapsed, the slot is a single shared entry rather than
161
+ // vertex_count entries.
162
+ let position_offset = 0u;
163
+ var attr_offset: u32 = vertex_count * 3u;
164
+
165
+ let normal_offset = attr_offset;
166
+ let normal_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_NORMAL}u) != 0u;
167
+ attr_offset = attr_offset + select(vertex_count, 1u, normal_compressed);
168
+
169
+ let tangent_offset = attr_offset;
170
+ let tangent_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TANGENT}u) != 0u;
171
+ attr_offset = attr_offset + select(vertex_count, 1u, tangent_compressed);
172
+
173
+ let color_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_COLOR}u) != 0u;
174
+ attr_offset = attr_offset + select(vertex_count, 1u, color_compressed);
175
+
176
+ let uv0_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_0}u) != 0u;
177
+ attr_offset = attr_offset + select(vertex_count * 2u, 2u, uv0_compressed);
178
+
179
+ let uv1_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_TEXTURE_COORDINATES_1}u) != 0u;
180
+ attr_offset = attr_offset + select(vertex_count, 1u, uv1_compressed);
181
+
182
+ let joints_offset = attr_offset;
183
+ let joints_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_JOINTS}u) != 0u;
184
+ attr_offset = attr_offset + select(vertex_count * 2u, 2u, joints_compressed);
185
+
186
+ let weights_offset = attr_offset;
187
+ let weights_compressed = (source_meshlet.flags & ${MESHLET_METADATA_FLAGS.ATTRIBUTE_COMPRESSED_SKINNING_WEIGHTS}u) != 0u;
188
+
189
+ let skin_base = binding.skin_matrix_offset;
190
+
191
+ // Resolve the clone meshlet's per-meshlet base offset in the
192
+ // prev_positions side-buffer (or sentinel if this meshlet
193
+ // somehow isn't a clone — shouldn't happen in practice since
194
+ // the skinning shader only runs on (clone, skinned) pairs, but
195
+ // the check costs one load and one branch). Lifted out of the
196
+ // vertex loop because it's per-meshlet, not per-vertex.
197
+ let clone_meshlet_abs = binding.clone_meshlet_metadata_offset + meshlet_local;
198
+ let prev_pos_meshlet_base = prev_position_offsets[clone_meshlet_abs];
199
+ let has_prev_positions = prev_pos_meshlet_base != 0xFFFFFFFFu;
200
+
201
+ // Stride loop — one thread per WORKGROUP_SIZE-stride'd vertex slot.
202
+ // Most meshlets fit in a single iteration (vertex_count <=
203
+ // WORKGROUP_SIZE); larger ones cycle the workgroup over them.
204
+ var v: u32 = local_id;
205
+ loop {
206
+ if v >= vertex_count { break; }
207
+
208
+ // Joint indices: vec4<u16> packed into 2 u32s. When the
209
+ // attribute is compressed (every vertex of this meshlet
210
+ // shares the same palette), there's one shared pair at
211
+ // joints_offset rather than per-vertex strides; broadcast
212
+ // that single entry to every thread.
213
+ let joints_local_offset = select(v * 2u, 0u, joints_compressed);
214
+ let joints_addr = source_attr_section + joints_offset + joints_local_offset;
215
+ let raw_j0 = meshlet_data[joints_addr];
216
+ let raw_j1 = meshlet_data[joints_addr + 1u];
217
+ let j0 = raw_j0 & 0xFFFFu;
218
+ let j1 = (raw_j0 >> 16u) & 0xFFFFu;
219
+ let j2 = raw_j1 & 0xFFFFu;
220
+ let j3 = (raw_j1 >> 16u) & 0xFFFFu;
221
+
222
+ // Joint weights: vec4<unorm8> packed into 1 u32. Half the
223
+ // bandwidth of the previous f16×4 layout, sufficient
224
+ // precision for typical character skinning (step size
225
+ // ~0.4%, well under any visible artefact threshold).
226
+ // unpack4x8unorm returns vec4<f32> in [0, 1] with
227
+ // components matching byte 0..3 of the packed u32.
228
+ let weights_local_offset = select(v, 0u, weights_compressed);
229
+ let weights_addr = source_attr_section + weights_offset + weights_local_offset;
230
+ let weights = unpack4x8unorm(meshlet_data[weights_addr]);
231
+
232
+ // Build per-joint dual quaternions from the joint matrices, then
233
+ // blend with antipodal correction. The result represents the
234
+ // weighted-average screw motion the four matrices encode.
235
+ let dq_blend = skin_blend_mesh_local(
236
+ inv_mesh_world,
237
+ skin_matrices[skin_base + j0],
238
+ skin_matrices[skin_base + j1],
239
+ skin_matrices[skin_base + j2],
240
+ skin_matrices[skin_base + j3],
241
+ weights,
242
+ );
243
+
244
+ // Position: full screw motion (rotation + translation).
245
+ let pos_addr = source_attr_section + position_offset + v * 3u;
246
+ let p = vec3f(
247
+ bitcast<f32>(meshlet_data[pos_addr]),
248
+ bitcast<f32>(meshlet_data[pos_addr + 1u]),
249
+ bitcast<f32>(meshlet_data[pos_addr + 2u]),
250
+ );
251
+ // Position: full screw motion, already mesh-local — the
252
+ // rasterizer reapplies mesh.world to every mesh's vertices,
253
+ // skinned or not.
254
+ let p_skinned = dual_quat_transform_point(dq_blend, p);
255
+
256
+ let clone_pos_addr = clone_attr_section + position_offset + v * 3u;
257
+ meshlet_data[clone_pos_addr] = bitcast<u32>(p_skinned.x);
258
+ meshlet_data[clone_pos_addr + 1u] = bitcast<u32>(p_skinned.y);
259
+ meshlet_data[clone_pos_addr + 2u] = bitcast<u32>(p_skinned.z);
260
+
261
+ // Previous-frame mesh-local position: same dual-quat math,
262
+ // but driven by prev_skin_matrices instead of skin_matrices.
263
+ // Written to the side-buffer at the per-meshlet base
264
+ // offset + v * 3 u32 words. The velocity post-pass reads
265
+ // these to interpolate per-vertex skin deformation across
266
+ // the rasterized triangle (vs. just the rigid mesh.global
267
+ // → mesh.prev_global delta that Stage 1's path captures).
268
+ //
269
+ // Skipping the write when there's no slot for this meshlet
270
+ // (non-clone — shouldn't be hit by this shader's dispatch
271
+ // set, but the guard is cheap). The dual-quat build is
272
+ // also skipped to save the per-joint work.
273
+ if has_prev_positions {
274
+ // Mesh-local for the same reason the current-frame
275
+ // position is — the velocity pass reads these through
276
+ // the same rasterizer transform, so the space they live
277
+ // in has to match.
278
+ let prev_dq_blend = skin_blend_mesh_local(
279
+ inv_mesh_world,
280
+ prev_skin_matrices[skin_base + j0],
281
+ prev_skin_matrices[skin_base + j1],
282
+ prev_skin_matrices[skin_base + j2],
283
+ prev_skin_matrices[skin_base + j3],
284
+ weights,
285
+ );
286
+
287
+ let p_prev_skinned = dual_quat_transform_point(prev_dq_blend, p);
288
+
289
+ let prev_addr = prev_pos_meshlet_base + v * 3u;
290
+ prev_positions[prev_addr] = bitcast<u32>(p_prev_skinned.x);
291
+ prev_positions[prev_addr + 1u] = bitcast<u32>(p_prev_skinned.y);
292
+ prev_positions[prev_addr + 2u] = bitcast<u32>(p_prev_skinned.z);
293
+ }
294
+
295
+ // Normal: rotation only. Skip when the source compressed it
296
+ // down to a single shared slot — per-vertex writes to that
297
+ // slot would race, and the rotated direction would be
298
+ // mostly-meaningless anyway since every vertex would compute
299
+ // a different one.
300
+ //
301
+ // Mesh-local like the position, and left there: the
302
+ // rasterizer applies the inverse-transpose of mesh.world's
303
+ // 3x3 to it, which is the glTF normal transform and takes a
304
+ // mesh-local direction to world (see fragment_gbuffer.js).
305
+ if !normal_compressed {
306
+ let n_raw_addr = source_attr_section + normal_offset + v;
307
+ let n = decode_vertex_normal(meshlet_data[n_raw_addr]);
308
+ let n_skinned = normalize(dual_quat_transform_direction(dq_blend, n));
309
+
310
+ let clone_n_addr = clone_attr_section + normal_offset + v;
311
+ meshlet_data[clone_n_addr] = encode_vertex_normal(n_skinned);
312
+ }
313
+
314
+ // Tangent: rotation only on the xyz; w (handedness) is a
315
+ // topological property, copied through unchanged.
316
+ if !tangent_compressed {
317
+ let t_raw_addr = source_attr_section + tangent_offset + v;
318
+ let t = decode_vertex_tangent(meshlet_data[t_raw_addr]);
319
+ let t_skinned_dir = normalize(dual_quat_transform_direction(dq_blend, t.xyz));
320
+
321
+ let clone_t_addr = clone_attr_section + tangent_offset + v;
322
+ meshlet_data[clone_t_addr] = encode_vertex_tangent(vec4f(t_skinned_dir, t.w));
323
+ }
324
+
325
+ v = v + ${SKINNING_WORKGROUP_SIZE}u;
326
+ }
327
+ }
328
+ `, [
329
+ MESHLET_METADATA_STRUCT.declaration_chunk,
330
+ SKINNING_BINDING_STRUCT.declaration_chunk,
331
+ chunk_meshlet_compute_attribute_section_offset,
332
+ chunk_skin_blend_mesh_local,
333
+ chunk_dual_quat_transform_point,
334
+ chunk_dual_quat_transform_direction,
335
+ chunk_decode_vertex_normal,
336
+ chunk_encode_vertex_normal,
337
+ chunk_decode_vertex_tangent,
338
+ chunk_encode_vertex_tangent,
339
+ chunk_scene_database_access,
340
+ chunk_mat4_inverse,
341
+ ]
342
+ );
343
+
344
+ /**
345
+ * Compute shader that deforms cloned meshlet vertex regions in place
346
+ * using the per-frame skin matrices the prep pass produced. One
347
+ * workgroup per (binding, meshlet) pair, dispatched against a flat
348
+ * `dispatch_lookup` table the host builds when bindings change.
349
+ *
350
+ * Skins position (full transform) plus normal and tangent (rotation
351
+ * only) per vertex via dual-quaternion blending — the blended DQ
352
+ * captures the four per-vertex joints' screw motions weighted by
353
+ * the per-vertex `SkinningWeights`, and applies cleanly to both
354
+ * positions and unit-direction attributes without the matrix-blend
355
+ * candy-wrapper artifacts that would otherwise show up in the
356
+ * normal-transformed lighting.
357
+ *
358
+ * Source bytes stay untouched — other instances pointed at the
359
+ * source render normally. Per-meshlet bounds aren't refreshed by
360
+ * this pass; meshlets that animate outside the source bounds may
361
+ * cull incorrectly until a future bounds-refresh phase lands.
362
+ *
363
+ * Dispatch via `shader.dispatch({ group_count_x: total_meshlet_count, ... })`
364
+ * where `total_meshlet_count` is the sum of `meshlet_count` across
365
+ * every registered binding. Skip when `total_meshlet_count === 0`.
366
+ */
367
+ export const shader_meshlet_apply_skinning = ComputeShader.from({
368
+ label: "Skinning/MeshletApplySkinning",
369
+ resources,
370
+ body,
371
+ });