@woosh/meep-engine 3.16.0 → 3.17.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 (410) hide show
  1. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  3. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  4. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  7. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  10. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  11. package/editor/view/node-graph/NodeGraphView.js +84 -8
  12. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  15. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  16. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  17. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  20. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  22. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  25. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  27. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  28. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  29. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  30. package/editor/view/particles/effect/particle-editor.css +123 -35
  31. package/package.json +1 -1
  32. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  34. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  35. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  37. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  38. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  39. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  40. package/src/core/graph/csr/CSRGraph.js +14 -6
  41. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  44. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  47. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  50. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  53. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  55. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  56. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  57. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  59. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  62. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  65. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  68. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  70. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  71. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  74. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  76. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  77. package/src/engine/animation/AnimationUtils.js +246 -246
  78. package/src/engine/animation/Animations.js +74 -74
  79. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  80. package/src/engine/ecs/EventType.d.ts +0 -1
  81. package/src/engine/ecs/EventType.js +1 -26
  82. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  85. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  86. package/src/engine/ecs/parent/EntityNode.js +4 -9
  87. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  89. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  93. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  95. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  96. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  97. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  98. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  99. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  100. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  101. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  102. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  103. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  104. package/src/engine/graphics3/LightSystem.js +4 -8
  105. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/MeshSystem.js +9 -19
  107. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  109. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  111. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  112. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  113. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  114. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  115. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  116. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  117. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  118. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  119. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  120. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  121. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  122. package/src/shade/playground/particle_editor/README.md +43 -2
  123. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  124. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  125. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  126. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  127. package/src/shade/renderer/Renderer.js +13 -1
  128. package/src/shade/renderer/particles/DESIGN.md +104 -19
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  130. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  131. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  132. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  133. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  134. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  135. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  136. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  137. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  138. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  139. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  140. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  141. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  142. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  143. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  144. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  145. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  146. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  148. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  149. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  151. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  153. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  155. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  157. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  158. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  159. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  160. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  161. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  162. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  163. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  164. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  165. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  166. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  168. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  169. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  170. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  171. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  172. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  174. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  175. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  176. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  177. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  178. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  179. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  180. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  181. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  182. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  183. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  185. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  186. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  187. package/src/shade/renderer/particles/graph_particles.js +105 -262
  188. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  189. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  190. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  191. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  192. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  193. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  194. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  196. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  197. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  198. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  199. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  200. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  202. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  203. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  205. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  206. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  207. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  208. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  209. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  210. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  211. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  212. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  213. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  214. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  215. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  216. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  217. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  218. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  219. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  220. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  221. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  222. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  223. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  224. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  225. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  226. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  227. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  228. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  229. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  230. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  235. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  236. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  237. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  238. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  239. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  240. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  241. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  242. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  244. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  245. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  246. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  247. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  248. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  250. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  251. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  252. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  253. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  254. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  255. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  256. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  257. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  258. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  259. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  261. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  262. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  263. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  264. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  265. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  266. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  267. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  268. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  270. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  271. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  272. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  273. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  274. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  275. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  276. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  277. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  279. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  280. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  281. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  282. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  283. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  285. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  286. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  287. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  288. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  289. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  291. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +26 -0
  292. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +1 -1
  293. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  294. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  295. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  296. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  297. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  298. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  299. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  300. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  302. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  303. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  305. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  306. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  307. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  308. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  309. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  310. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  311. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  312. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  314. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  315. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  317. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  318. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  320. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  321. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  323. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  324. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  325. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  326. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  327. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  328. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  329. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  330. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  331. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  332. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  333. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  334. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  335. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  336. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  337. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  338. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  339. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  340. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  341. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  342. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  343. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  344. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  345. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  346. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  347. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  348. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  349. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  350. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  351. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  352. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  353. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  354. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  355. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  356. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  357. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  374. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  375. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  376. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  377. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  378. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  379. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  380. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  381. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  382. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  383. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  384. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  385. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  386. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  387. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  388. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  389. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  390. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  391. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  392. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  393. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  394. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  395. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  396. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  397. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  398. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  399. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  400. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  401. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  402. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  403. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  404. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  405. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  406. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  407. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  408. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  409. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  410. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -1,517 +1,524 @@
1
- import { ParticleGroupBuilder } from "../ParticleGroupBuilder.js";
2
- import { PARTICLE_TYPE_F32, PARTICLE_TYPE_VEC3, PARTICLE_TYPE_VEC4 } from "../ParticleValueType.js";
3
-
4
- /**
5
- * Simplex noise, and the curl fields built out of it.
6
- *
7
- * These are the big ones. `simplex3` is around a hundred and thirty instructions and `curl4` is
8
- * something over two thousand, because a curl needs twelve noise samples and there is no loop to put
9
- * them in. Nothing here is cheap and nothing here pretends to be; what they are is *correct*, and
10
- * available as one node instead of as a page of arithmetic an author has to get right themselves.
11
- *
12
- * ## Where the algorithm comes from
13
- *
14
- * Simplex noise is Ken Perlin's, and the formulation is Stefan Gustavson and Ashima Arts' well-known
15
- * GLSL one — the same skew, the same permutation polynomial and the same gradient table, so a field
16
- * produced here matches one produced by that shader lane for lane. It is transcribed rather than
17
- * reinvented: an author who has seen simplex noise in a shader should get the noise they expect, and
18
- * a hash or a skew of my own would be a different function wearing the same name.
19
- *
20
- * ## Why it is written the way it is
21
- *
22
- * The shader version keeps four corners in flight at once — four offsets, four gradients, four
23
- * weights — as `vec4`s across corners. Done that way here it needs upwards of forty-five registers,
24
- * and the register file is the interpreter's single largest cost (see
25
- * {@link ../../ParticleConstants.js PARTICLE_VM_REGISTER_SLOTS}). So the corners are summed one at a
26
- * time instead: each corner's offset, gradient and weight are computed, folded into a running total,
27
- * and dropped. The result is identical and the high-water mark is far lower, which is the difference
28
- * between a node that works and a node that reports it has run out of registers.
29
- *
30
- * The permutation itself stays a `vec4` — it is one hash of four corners at once and splitting it
31
- * would quadruple the work rather than move it.
32
- */
33
-
34
- /** Skew factor for the 3-simplex lattice: `1/3`. @type {number} */
35
- const F3 = 1 / 3;
36
-
37
- /** Unskew factor for the 3-simplex lattice: `1/6`. @type {number} */
38
- const G3 = 1 / 6;
39
-
40
- /** Skew factor for the 4-simplex lattice: `(sqrt(5) - 1) / 4`. @type {number} */
41
- const F4 = 0.309016994374947451;
42
-
43
- /** Unskew factor for the 4-simplex lattice: `(5 - sqrt(5)) / 20`. @type {number} */
44
- const G4 = 0.138196601125011;
45
-
46
- /**
47
- * `x mod 289`, without a modulo opcode: `x - floor(x / 289) * 289`.
48
- *
49
- * 289 is 17 squared, and the permutation polynomial below is built so that its results stay inside
50
- * `[0, 289)` — which is what keeps the whole hash inside the range a float represents exactly.
51
- *
52
- * @param {ParticleGroupBuilder} g
53
- * @param {*} x
54
- * @returns {*}
55
- */
56
- function mod289(g, x) {
57
- return g.sub(x, g.mul(g.floor(g.mul(x, g.f(1 / 289))), g.f(289)));
58
- }
59
-
60
- /**
61
- * The permutation polynomial: `(34x + 1) x mod 289`.
62
- *
63
- * A hash chosen so that composing it over the lattice coordinates spreads corners across the
64
- * gradient table without a lookup — there are no tables in this VM to look one up in.
65
- *
66
- * @param {ParticleGroupBuilder} g
67
- * @param {*} x
68
- * @returns {*}
69
- */
70
- function permute(g, x) {
71
- // `(34x + 1)x` written as `34x*x + x`, which is the same number and costs four fewer registers.
72
- //
73
- // A constant meeting a vector has to be broadcast into as many registers as the vector is wide,
74
- // unless the operation is a multiply — a scalar times a vector lowers to SCALE, which reads its
75
- // scalar as one lane. Written the obvious way, the `+ 1` is a four-register constant alive at the
76
- // busiest moment of the function. Written this way there is no additive constant at all, and the
77
- // `34` is a SCALE operand. See the note about the register file at the top of this file.
78
- return mod289(g, g.mad(g.mul(x, g.f(34)), x, x));
79
- }
80
-
81
- /**
82
- * `1/sqrt(r)` to first order about the gradient lengths this actually sees — the standard
83
- * `1.79284291400159 - 0.85373472095314 r`.
84
- *
85
- * The gradients come off a fixed table and their squared lengths land in a narrow band, so a line
86
- * through that band normalises them well enough that no visible difference survives; an exact
87
- * reciprocal square root would be a divide and a `sqrt` per corner instead of one instruction.
88
- *
89
- * @param {ParticleGroupBuilder} g
90
- * @param {*} r
91
- * @returns {*}
92
- */
93
- function taylor_inv_sqrt(g, r) {
94
- return g.mad(g.f(-0.85373472095314), r, 1.79284291400159);
95
- }
96
-
97
- /**
98
- * One corner's contribution to a simplex sum: `max(0.6 - |x|^2, 0)^4 * dot(gradient, x)`.
99
- *
100
- * The fourth power of a radial falloff that reaches zero at the edge of a corner's influence, which
101
- * is what makes the sum continuous and its derivative continuous with it.
102
- *
103
- * @param {ParticleGroupBuilder} g
104
- * @param {*} offset the vector from the corner to the point
105
- * @param {*} gradient the corner's gradient, already normalised
106
- * @returns {*} a scalar
107
- */
108
- function corner_contribution(g, offset, gradient) {
109
- const t = g.max(g.sub(g.f(0.6), g.dot(offset, offset)), 0);
110
-
111
- const t2 = g.mul(t, t);
112
-
113
- return g.mul(g.mul(t2, t2), g.dot(gradient, offset));
114
- }
115
-
116
- /**
117
- * The 3D gradient for one corner, from its hashed index.
118
- *
119
- * The table is the 7x7 grid of points on the faces of an octahedron that Gustavson's formulation
120
- * uses: the index is split into a row and a column, those become two coordinates, the third is
121
- * whatever is left of the L1 norm, and the sign of that third one flips the first two. Every step is
122
- * arithmetic, because there is nowhere to put a table.
123
- *
124
- * @param {ParticleGroupBuilder} g
125
- * @param {*} index a hashed corner index, in `[0, 289)`
126
- * @returns {*} a `vec3` gradient, not yet normalised
127
- */
128
- function grad3(g, index) {
129
- const N = 1 / 7;
130
-
131
- const ns_x = N * 2;
132
- const ns_y = N * 0.5 - 1;
133
- const ns_z = N;
134
-
135
- // j = index mod 49, the 7x7 grid
136
- const j = g.sub(index, g.mul(g.f(49), g.floor(g.mul(index, g.f(ns_z * ns_z)))));
137
-
138
- const row = g.floor(g.mul(j, g.f(ns_z)));
139
- const col = g.floor(g.sub(j, g.mul(g.f(7), row)));
140
-
141
- const gx = g.mad(row, g.f(ns_x), g.f(ns_y));
142
- const gy = g.mad(col, g.f(ns_x), g.f(ns_y));
143
-
144
- // what is left of the unit L1 ball is the third coordinate
145
- const gz = g.sub(g.sub(1, g.abs(gx)), g.abs(gy));
146
-
147
- // Outside the ball the point belongs to the opposite face: reflect the first two coordinates.
148
- // At or below zero, not below — the coordinates land on a seventh and the boundary is reachable
149
- // exactly, so which side it falls on is a decision rather than a rounding accident.
150
- const outside = g.op("compare", [gz, 0], { op: "le" });
151
-
152
- const flip = g.mul(g.mad(g.floor(gx), 2, 1), outside);
153
- const flop = g.mul(g.mad(g.floor(gy), 2, 1), outside);
154
-
155
- return g.vec3(g.sub(gx, flip), g.sub(gy, flop), gz);
156
- }
157
-
158
- /**
159
- * Simplex noise over three dimensions, in roughly `[-1, 1]`.
160
- *
161
- * @param {ParticleGroupBuilder} g
162
- * @param {*} v a `vec3` position
163
- * @returns {*} a scalar
164
- */
165
- function simplex3(g, v) {
166
- // Which cell of the skewed lattice the point is in, and where it sits inside it.
167
- const cell = g.floor(g.add(v, g.dot(v, g.f(F3))));
168
- const x0 = g.add(g.sub(v, cell), g.dot(cell, g.f(G3)));
169
-
170
- // The simplex a point falls in is decided by the order of its coordinates: the two steps from
171
- // the cell's corner to the point's own are the two largest axes, in order.
172
- const order = g.step(g.swz(x0, "yzx"), x0);
173
- const rest = g.sub(g.f(1), order);
174
-
175
- const step1 = g.min(order, g.swz(rest, "zxy"));
176
- const step2 = g.max(order, g.swz(rest, "zxy"));
177
-
178
- const cell289 = mod289(g, cell);
179
-
180
- // One hash of all four corners at once. Each `permute` folds in one lattice axis.
181
- const corners = g.vec4(g.f(0), g.z(step1), g.z(step2), g.f(1));
182
-
183
- let hashed = permute(g, g.add(g.z(cell289), corners));
184
-
185
- hashed = permute(g, g.add(g.add(hashed, g.y(cell289)),
186
- g.vec4(g.f(0), g.y(step1), g.y(step2), g.f(1))));
187
-
188
- hashed = permute(g, g.add(g.add(hashed, g.x(cell289)),
189
- g.vec4(g.f(0), g.x(step1), g.x(step2), g.f(1))));
190
-
191
- // Corner by corner, so that one corner's working set is all that is ever live at once.
192
- const offsets = [
193
- x0,
194
- g.add(g.sub(x0, step1), g.f(G3)),
195
- g.add(g.sub(x0, step2), g.f(G3 * 2)),
196
- g.sub(x0, g.f(0.5)),
197
- ];
198
-
199
- let total = null;
200
-
201
- ["x", "y", "z", "w"].forEach((lane, corner) => {
202
- const gradient = grad3(g, g.swz(hashed, lane));
203
-
204
- const normalized = g.scale(gradient, taylor_inv_sqrt(g, g.dot(gradient, gradient)));
205
-
206
- const contribution = corner_contribution(g, offsets[corner], normalized);
207
-
208
- total = total === null ? contribution : g.add(total, contribution);
209
- });
210
-
211
- // scales the sum to about [-1, 1]
212
- return g.mul(g.f(42), total);
213
- }
214
-
215
- /**
216
- * The 4D gradient for one corner, from its hashed index.
217
- *
218
- * The table this time is the 32 vertices of a hypercube's cell centres, reached the same way: three
219
- * coordinates from base-7 digits of the index, the fourth from what is left of the L1 norm, and its
220
- * sign flipping the other three.
221
- *
222
- * @param {ParticleGroupBuilder} g
223
- * @param {*} index
224
- * @returns {*} a `vec4` gradient, not yet normalised
225
- */
226
- function grad4(g, index) {
227
- // 1/294, 1/49, 1/7: successive base-7 digits of the index
228
- const digits = g.mul(g.f(1 / 294, 1 / 49, 1 / 7), index);
229
-
230
- // written as scale-then-add rather than as multiply-adds, for the register reason in `permute`
231
- const xyz = g.sub(g.mul(g.floor(g.mul(g.fract(digits), g.f(7))), g.f(1 / 7)), 1);
232
-
233
- const w = g.sub(g.f(1.5), g.dot(g.abs(xyz), g.f(1)));
234
-
235
- const negative = g.op("compare", [w, 0], { op: "lt" });
236
-
237
- const sign = g.sub(g.mul(g.op("compare", [xyz, g.f(0)], { op: "lt" }), g.f(2)), 1);
238
-
239
- const flipped = g.add(g.mul(sign, negative), xyz);
240
-
241
- return g.vec4(g.x(flipped), g.y(flipped), g.z(flipped), w);
242
- }
243
-
244
- /**
245
- * Simplex noise over four dimensions, in roughly `[-1, 1]`.
246
- *
247
- * The fourth dimension is what a moving noise field is made of: hold three of them at a position and
248
- * walk the fourth with time, and the field changes rather than sliding past.
249
- *
250
- * @param {ParticleGroupBuilder} g
251
- * @param {*} v a `vec4` position
252
- * @returns {*} a scalar
253
- */
254
- function simplex4(g, v) {
255
- const cell = g.floor(g.add(v, g.dot(v, g.f(F4))));
256
- const x0 = g.add(g.sub(v, cell), g.dot(cell, g.f(G4)));
257
-
258
- // The rank of each coordinate among the four, by comparing every pair once. A coordinate's rank
259
- // is how many of the others it is at least as large as, and the ranks are the order the four
260
- // steps from the cell's corner are taken in.
261
- const isX = g.step(g.swz(x0, "yzw"), g.swz(x0, "xxx"));
262
- const isYZ = g.step(g.swz(x0, "zww"), g.swz(x0, "yyz"));
263
-
264
- const one_minus = ref => g.sub(g.f(1), ref);
265
-
266
- const rank_x = g.add(g.add(g.x(isX), g.y(isX)), g.z(isX));
267
- const rank_y = g.add(one_minus(g.x(isX)), g.add(g.x(isYZ), g.y(isYZ)));
268
- const rank_z = g.add(one_minus(g.y(isX)), g.add(one_minus(g.x(isYZ)), g.z(isYZ)));
269
- const rank_w = g.add(one_minus(g.z(isX)), g.add(one_minus(g.y(isYZ)), one_minus(g.z(isYZ))));
270
-
271
- const rank = g.vec4(rank_x, rank_y, rank_z, rank_w);
272
-
273
- // A rank is a whole number in 0..3, and the three steps from the cell's corner are the
274
- // coordinates whose rank has been passed: the first step takes rank 3, the second 3 and 2, the
275
- // third all but rank 0. `clamp(rank - k, 0, 1)` says that, and a clamp reads both of its bounds
276
- // at once — so both constants are broadcast to four lanes and live at the same moment. Knowing
277
- // the range removes one bound from two of the three.
278
- const step_taken = (value, first, last) => {
279
- const floored = first ? value : g.max(value, g.f(0));
280
-
281
- return last ? floored : g.min(floored, g.f(1));
282
- };
283
-
284
- // The permutation needs one lane of each of the three steps at a time, so it takes them off the
285
- // ranks directly rather than off three assembled vectors. Those vectors are twelve registers,
286
- // and holding them through the permutation — the busiest part of the function — is what put this
287
- // over the register file. Built this way the ranks are all that has to survive it, and the
288
- // vectors are assembled afterwards for the corner offsets, by which point the hash is done.
289
- const steps_of = axis => {
290
- const r = g.swz(rank, axis);
291
-
292
- return g.vec4(
293
- step_taken(g.sub(r, g.f(2)), false, true),
294
- step_taken(g.sub(r, g.f(1)), false, false),
295
- step_taken(r, true, false),
296
- g.f(1)
297
- );
298
- };
299
-
300
- const cell289 = mod289(g, cell);
301
-
302
- // The corner at the cell's own origin hashes on its own; the other four hash together.
303
- let origin = permute(g, g.w(cell289));
304
-
305
- origin = permute(g, g.add(origin, g.z(cell289)));
306
- origin = permute(g, g.add(origin, g.y(cell289)));
307
- origin = permute(g, g.add(origin, g.x(cell289)));
308
-
309
- let hashed = permute(g, g.add(g.w(cell289), steps_of("w")));
310
-
311
- hashed = permute(g, g.add(g.add(hashed, g.z(cell289)), steps_of("z")));
312
- hashed = permute(g, g.add(g.add(hashed, g.y(cell289)), steps_of("y")));
313
- hashed = permute(g, g.add(g.add(hashed, g.x(cell289)), steps_of("x")));
314
-
315
- const step3 = step_taken(rank, true, false);
316
- const step2 = step_taken(g.sub(rank, g.f(1)), false, false);
317
- const step1 = step_taken(g.sub(rank, g.f(2)), false, true);
318
-
319
- const offsets = [
320
- x0,
321
- g.add(g.sub(x0, step1), g.f(G4)),
322
- g.add(g.sub(x0, step2), g.f(G4 * 2)),
323
- g.add(g.sub(x0, step3), g.f(G4 * 3)),
324
- g.add(x0, g.f(G4 * 4 - 1)),
325
- ];
326
-
327
- const indices = [origin, g.x(hashed), g.y(hashed), g.z(hashed), g.w(hashed)];
328
-
329
- let total = null;
330
-
331
- for (let corner = 0; corner < 5; corner++) {
332
- const gradient = grad4(g, indices[corner]);
333
-
334
- const normalized = g.scale(gradient, taylor_inv_sqrt(g, g.dot(gradient, gradient)));
335
-
336
- const contribution = corner_contribution(g, offsets[corner], normalized);
337
-
338
- total = total === null ? contribution : g.add(total, contribution);
339
- }
340
-
341
- return g.mul(g.f(49), total);
342
- }
343
-
344
- /**
345
- * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
346
- */
347
- export const PARTICLE_GROUP_SIMPLEX_3 = (() => {
348
- const g = new ParticleGroupBuilder({
349
- name: "simplex3",
350
- label: "Simplex Noise 3D",
351
- icon: "M12 3l8 4.5v9L12 21l-8-4.5v-9z M12 3v18 M4 7.5l16 9 M20 7.5l-16 9",
352
- help: "simplex noise at a point in space, about [-1, 1]",
353
- inputs: [{ name: "p", type: PARTICLE_TYPE_VEC3 }],
354
- outputs: [{ name: "out", type: PARTICLE_TYPE_F32 }],
355
- });
356
-
357
- g.out("out", simplex3(g, g.in.p));
358
-
359
- return g.build();
360
- })();
361
-
362
- /**
363
- * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
364
- */
365
- export const PARTICLE_GROUP_SIMPLEX_4 = (() => {
366
- const g = new ParticleGroupBuilder({
367
- name: "simplex4",
368
- label: "Simplex Noise 4D",
369
- icon: "M12 3l8 4.5v9L12 21l-8-4.5v-9z M12 3v18 M4 7.5l16 9 M20 7.5l-16 9 M4 7.5l16 0",
370
- help: "simplex noise at a point in space and time, about [-1, 1]",
371
- inputs: [{ name: "p", type: PARTICLE_TYPE_VEC4 }],
372
- outputs: [{ name: "out", type: PARTICLE_TYPE_F32 }],
373
- });
374
-
375
- g.out("out", simplex4(g, g.in.p));
376
-
377
- return g.build();
378
- })();
379
-
380
- /**
381
- * How far apart the samples taken either side of a point are, when differencing the potential.
382
- *
383
- * Small enough that the difference is a derivative and large enough that it is not two samples of
384
- * the same value: simplex noise varies over a unit cell, and a tenth of one is a reasonable step in
385
- * single precision.
386
- *
387
- * @type {number}
388
- */
389
- const CURL_EPSILON = 0.1;
390
-
391
- /**
392
- * Offsets that turn one noise field into three independent ones.
393
- *
394
- * A curl needs a vector potential, and there is one noise function. Sampling it at three points far
395
- * enough apart that the values are uncorrelated is the standard way to get three fields out of it —
396
- * the numbers are arbitrary and only have to be large and unalike.
397
- *
398
- * @type {number[][]}
399
- */
400
- const CURL_FIELD_OFFSETS = [
401
- [0, 0, 0],
402
- [31.416, 47.853, 12.793],
403
- [83.147, 19.264, 65.037],
404
- ];
405
-
406
- /**
407
- * The curl of a noise field, as a vector.
408
- *
409
- * A curl is divergence-free — nothing anywhere is a source or a sink — which is what makes it look
410
- * like a fluid rather than like noise. Particles advected by one swirl and never pile up, and that
411
- * is the whole reason to spend this many instructions on it.
412
- *
413
- * ## What it costs
414
- *
415
- * Twelve noise samples. The curl of a three-component potential needs six partial derivatives, each
416
- * a difference of two samples, and no two of them are of the same field at the same place. There is
417
- * no loop to put them in and no way to share them, so the group is twelve copies of the noise and
418
- * the arithmetic between them — well over a thousand instructions, and worth knowing before it is
419
- * put in an UPDATE graph that runs on every particle every frame.
420
- *
421
- * @param {ParticleGroupBuilder} g
422
- * @param {function(ParticleGroupBuilder, number, number, number, number): *} sample
423
- * the potential's `field`th component at `p + (dx, dy, dz)`, as a scalar
424
- * @returns {*} a `vec3`
425
- */
426
- function curl_of(g, sample) {
427
- const e = CURL_EPSILON;
428
-
429
- /**
430
- * The centred difference of one field along one axis, *not* divided by the step — every one of
431
- * the six is over the same step, so the division comes out of the sum at the end as a single
432
- * scale rather than six multiplies.
433
- *
434
- * @param {number} field
435
- * @param {number} axis 0, 1 or 2
436
- * @returns {*}
437
- */
438
- const difference = (field, axis) => {
439
- const step = [0, 0, 0];
440
-
441
- step[axis] = e;
442
-
443
- const ahead = sample(g, field, step[0], step[1], step[2]);
444
- const behind = sample(g, field, -step[0], -step[1], -step[2]);
445
-
446
- return g.sub(ahead, behind);
447
- };
448
-
449
- const curl = g.vec3(
450
- g.sub(difference(2, 1), difference(1, 2)),
451
- g.sub(difference(0, 2), difference(2, 0)),
452
- g.sub(difference(1, 0), difference(0, 1))
453
- );
454
-
455
- return g.scale(curl, g.f(1 / (2 * e)));
456
- }
457
-
458
- /**
459
- * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
460
- */
461
- export const PARTICLE_GROUP_CURL_3 = (() => {
462
- const g = new ParticleGroupBuilder({
463
- name: "curl3",
464
- label: "Curl Noise 3D",
465
- icon: "M6 15c0-5 5-8 8-5s0 8-3 6-1-6 3-6",
466
- help: "a divergence-free swirl field — twelve noise samples, so not cheap",
467
- inputs: [{ name: "p", type: PARTICLE_TYPE_VEC3 }],
468
- outputs: [{ name: "out", type: PARTICLE_TYPE_VEC3 }],
469
- });
470
-
471
- g.out("out", curl_of(g, (builder, field, dx, dy, dz) => {
472
- const offset = CURL_FIELD_OFFSETS[field];
473
-
474
- const at = builder.add(builder.in.p, builder.f(offset[0] + dx, offset[1] + dy, offset[2] + dz));
475
-
476
- return builder.call(PARTICLE_GROUP_SIMPLEX_3, { p: at }).out;
477
- }));
478
-
479
- return g.build();
480
- })();
481
-
482
- /**
483
- * The same field, with a fourth coordinate to move it through.
484
- *
485
- * Advance `w` with time and the swirl evolves in place instead of drifting past — which is what
486
- * separates smoke from a conveyor belt of smoke.
487
- *
488
- * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
489
- */
490
- export const PARTICLE_GROUP_CURL_4 = (() => {
491
- const g = new ParticleGroupBuilder({
492
- name: "curl4",
493
- label: "Curl Noise 4D",
494
- icon: "M6 15c0-5 5-8 8-5s0 8-3 6-1-6 3-6 M18 5v4 M16 7h4",
495
- help: "a curl field that evolves along w — twelve 4D noise samples, so expensive",
496
- inputs: [
497
- { name: "p", type: PARTICLE_TYPE_VEC3 },
498
- { name: "w", type: PARTICLE_TYPE_F32 },
499
- ],
500
- outputs: [{ name: "out", type: PARTICLE_TYPE_VEC3 }],
501
- });
502
-
503
- g.out("out", curl_of(g, (builder, field, dx, dy, dz) => {
504
- const offset = CURL_FIELD_OFFSETS[field];
505
-
506
- const at = builder.vec4(
507
- builder.add(builder.x(builder.in.p), builder.f(offset[0] + dx)),
508
- builder.add(builder.y(builder.in.p), builder.f(offset[1] + dy)),
509
- builder.add(builder.z(builder.in.p), builder.f(offset[2] + dz)),
510
- builder.in.w
511
- );
512
-
513
- return builder.call(PARTICLE_GROUP_SIMPLEX_4, { p: at }).out;
514
- }));
515
-
516
- return g.build();
517
- })();
1
+ import { ParticleGroupBuilder } from "../ParticleGroupBuilder.js";
2
+ import { PARTICLE_TYPE_F32, PARTICLE_TYPE_VEC3, PARTICLE_TYPE_VEC4 } from "../ParticleValueType.js";
3
+
4
+ /**
5
+ * Simplex noise, and the curl fields built out of it.
6
+ *
7
+ * These are the big ones. `simplex3` is around a hundred and thirty instructions and `curl4` is
8
+ * something over two thousand, because a curl needs twelve noise samples and there is no loop to put
9
+ * them in. Nothing here is cheap and nothing here pretends to be; what they are is *correct*, and
10
+ * available as one node instead of as a page of arithmetic an author has to get right themselves.
11
+ *
12
+ * ## Where the algorithm comes from
13
+ *
14
+ * Simplex noise is Ken Perlin's, and the formulation is Stefan Gustavson and Ashima Arts' well-known
15
+ * GLSL one — the same skew, the same permutation polynomial and the same gradient table. It is
16
+ * transcribed rather than reinvented: an author who has seen simplex noise in a shader should get
17
+ * the noise they expect, and a hash or a skew of my own would be a different function wearing the
18
+ * same name.
19
+ *
20
+ * One thing is deliberately *not* transcribed. The GLSL normalises each corner's gradient with
21
+ * `taylorInvSqrt` — a straight line through the band the gradient table's squared lengths fall in,
22
+ * because a reciprocal square root was worth avoiding where that shader was written. This VM has
23
+ * `NORMALIZE` as one instruction, and it is exact. So the gradients here are normalised properly,
24
+ * which is four instructions fewer per corner and a field that differs from the GLSL's by that
25
+ * approximation's error — a fraction of a percent, in the direction of the noise the formulation
26
+ * describes rather than away from it. Everything that decides the *shape* of the field, which is to
27
+ * say which simplex a point falls in and which gradient each corner is handed, is transcribed
28
+ * exactly.
29
+ *
30
+ * ## Why it is written the way it is
31
+ *
32
+ * The shader version keeps four corners in flight at once — four offsets, four gradients, four
33
+ * weights — as `vec4`s across corners. Done that way here it needs upwards of forty-five registers,
34
+ * and the fast interpreter's register file is its single largest cost (see
35
+ * {@link ../../ParticleConstants.js PARTICLE_VM_FAST_REGISTER_SLOTS}). So the corners are summed one
36
+ * at a time instead: each corner's offset, gradient and weight are computed, folded into a running
37
+ * total, and dropped. The result is identical and the high-water mark is far lower, which is the
38
+ * difference between a node that runs on the fast interpreter and one that drags the whole system
39
+ * onto the wide one.
40
+ *
41
+ * The permutation itself stays a `vec4` — it is one hash of four corners at once and splitting it
42
+ * would quadruple the work rather than move it.
43
+ */
44
+
45
+ /** Skew factor for the 3-simplex lattice: `1/3`. @type {number} */
46
+ const F3 = 1 / 3;
47
+
48
+ /** Unskew factor for the 3-simplex lattice: `1/6`. @type {number} */
49
+ const G3 = 1 / 6;
50
+
51
+ /** Skew factor for the 4-simplex lattice: `(sqrt(5) - 1) / 4`. @type {number} */
52
+ const F4 = 0.309016994374947451;
53
+
54
+ /** Unskew factor for the 4-simplex lattice: `(5 - sqrt(5)) / 20`. @type {number} */
55
+ const G4 = 0.138196601125011;
56
+
57
+ /**
58
+ * `x mod 289`, without a modulo opcode: `x - floor(x / 289) * 289`.
59
+ *
60
+ * 289 is 17 squared, and the permutation polynomial below is built so that its results stay inside
61
+ * `[0, 289)` — which is what keeps the whole hash inside the range a float represents exactly.
62
+ *
63
+ * A multiply-add would spell the tail of this in one instruction rather than two, and it is not used:
64
+ * a `MAD` holds three operands at once where the multiply and the subtract hold two each, and the
65
+ * third is a four-lane literal. Measured, that is four instructions off `simplex3` and four registers
66
+ * onto it — the wrong side of the trade, for the reason the note at the top of this file gives.
67
+ *
68
+ * @param {ParticleGroupBuilder} g
69
+ * @param {*} x
70
+ * @returns {*}
71
+ */
72
+ function mod289(g, x) {
73
+ return g.sub(x, g.mul(g.floor(g.mul(x, g.f(1 / 289))), g.f(289)));
74
+ }
75
+
76
+ /**
77
+ * The permutation polynomial: `(34x + 1) x mod 289`.
78
+ *
79
+ * A hash chosen so that composing it over the lattice coordinates spreads corners across the
80
+ * gradient table without a lookup — there are no tables in this VM to look one up in.
81
+ *
82
+ * @param {ParticleGroupBuilder} g
83
+ * @param {*} x
84
+ * @returns {*}
85
+ */
86
+ function permute(g, x) {
87
+ // `(34x + 1)x` written as `34x*x + x`, which is the same number and costs four fewer registers.
88
+ //
89
+ // A constant meeting a vector has to be broadcast into as many registers as the vector is wide,
90
+ // unless the operation is a multiply — a scalar times a vector lowers to SCALE, which reads its
91
+ // scalar as one lane. Written the obvious way, the `+ 1` is a four-register constant alive at the
92
+ // busiest moment of the function. Written this way there is no additive constant at all, and the
93
+ // `34` is a SCALE operand. See the note about the register file at the top of this file.
94
+ return mod289(g, g.mad(g.mul(x, g.f(34)), x, x));
95
+ }
96
+
97
+ /**
98
+ * One corner's contribution to a simplex sum: `max(0.6 - |x|^2, 0)^4 * dot(gradient, x)`.
99
+ *
100
+ * The fourth power of a radial falloff that reaches zero at the edge of a corner's influence, which
101
+ * is what makes the sum continuous and its derivative continuous with it.
102
+ *
103
+ * @param {ParticleGroupBuilder} g
104
+ * @param {*} offset the vector from the corner to the point
105
+ * @param {*} gradient the corner's gradient, already normalised
106
+ * @returns {*} a scalar
107
+ */
108
+ function corner_contribution(g, offset, gradient) {
109
+ // `saturate` rather than `max(_, 0)`: the falloff is 0.6 less a squared length, so it can never
110
+ // reach the upper bound and the two are the same function. One instruction with its bounds in
111
+ // the instruction word, against a literal zero in a register and a `MAX` to compare it with.
112
+ const t = g.saturate(g.sub(g.f(0.6), g.dot(offset, offset)));
113
+
114
+ const t2 = g.mul(t, t);
115
+
116
+ return g.mul(g.mul(t2, t2), g.dot(gradient, offset));
117
+ }
118
+
119
+ /**
120
+ * The 3D gradient for one corner, from its hashed index.
121
+ *
122
+ * The table is the 7x7 grid of points on the faces of an octahedron that Gustavson's formulation
123
+ * uses: the index is split into a row and a column, those become two coordinates, the third is
124
+ * whatever is left of the L1 norm, and the sign of that third one flips the first two. Every step is
125
+ * arithmetic, because there is nowhere to put a table.
126
+ *
127
+ * @param {ParticleGroupBuilder} g
128
+ * @param {*} index a hashed corner index, in `[0, 289)`
129
+ * @returns {*} a `vec3` gradient, not yet normalised
130
+ */
131
+ function grad3(g, index) {
132
+ const N = 1 / 7;
133
+
134
+ const ns_x = N * 2;
135
+ const ns_y = N * 0.5 - 1;
136
+ const ns_z = N;
137
+
138
+ // j = index mod 49, the 7x7 grid
139
+ const j = g.sub(index, g.mul(g.f(49), g.floor(g.mul(index, g.f(ns_z * ns_z)))));
140
+
141
+ const row = g.floor(g.mul(j, g.f(ns_z)));
142
+ const col = g.floor(g.sub(j, g.mul(g.f(7), row)));
143
+
144
+ const gx = g.mad(row, g.f(ns_x), g.f(ns_y));
145
+ const gy = g.mad(col, g.f(ns_x), g.f(ns_y));
146
+
147
+ // what is left of the unit L1 ball is the third coordinate
148
+ const gz = g.sub(g.sub(1, g.abs(gx)), g.abs(gy));
149
+
150
+ // Outside the ball the point belongs to the opposite face: reflect the first two coordinates.
151
+ // At or below zero, not below — the coordinates land on a seventh and the boundary is reachable
152
+ // exactly, so which side it falls on is a decision rather than a rounding accident.
153
+ // Negated once and shared, so that each reflection is a single multiply-add — `c - k*outside`
154
+ // written as `k*(-outside) + c`. Both halves want it, which is what pays for the negate.
155
+ const inside = g.neg(g.op("compare", [gz, 0], { op: "le" }));
156
+
157
+ return g.vec3(
158
+ g.mad(g.mad(g.floor(gx), 2, 1), inside, gx),
159
+ g.mad(g.mad(g.floor(gy), 2, 1), inside, gy),
160
+ gz
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Simplex noise over three dimensions, in roughly `[-1, 1]`.
166
+ *
167
+ * @param {ParticleGroupBuilder} g
168
+ * @param {*} v a `vec3` position
169
+ * @returns {*} a scalar
170
+ */
171
+ function simplex3(g, v) {
172
+ // Which cell of the skewed lattice the point is in, and where it sits inside it.
173
+ const cell = g.floor(g.add(v, g.dot(v, g.f(F3))));
174
+ const x0 = g.add(g.sub(v, cell), g.dot(cell, g.f(G3)));
175
+
176
+ // The simplex a point falls in is decided by the order of its coordinates: the two steps from
177
+ // the cell's corner to the point's own are the two largest axes, in order.
178
+ const order = g.step(g.swz(x0, "yzx"), x0);
179
+ const rest = g.sub(g.f(1), order);
180
+
181
+ const step1 = g.min(order, g.swz(rest, "zxy"));
182
+ const step2 = g.max(order, g.swz(rest, "zxy"));
183
+
184
+ const cell289 = mod289(g, cell);
185
+
186
+ // One hash of all four corners at once. Each `permute` folds in one lattice axis.
187
+ const corners = g.vec4(g.f(0), g.z(step1), g.z(step2), g.f(1));
188
+
189
+ let hashed = permute(g, g.add(g.z(cell289), corners));
190
+
191
+ hashed = permute(g, g.add(g.add(hashed, g.y(cell289)),
192
+ g.vec4(g.f(0), g.y(step1), g.y(step2), g.f(1))));
193
+
194
+ hashed = permute(g, g.add(g.add(hashed, g.x(cell289)),
195
+ g.vec4(g.f(0), g.x(step1), g.x(step2), g.f(1))));
196
+
197
+ // Corner by corner, so that one corner's working set is all that is ever live at once.
198
+ const offsets = [
199
+ x0,
200
+ g.add(g.sub(x0, step1), g.f(G3)),
201
+ g.add(g.sub(x0, step2), g.f(G3 * 2)),
202
+ g.sub(x0, g.f(0.5)),
203
+ ];
204
+
205
+ let total = null;
206
+
207
+ ["x", "y", "z", "w"].forEach((lane, corner) => {
208
+ const gradient = grad3(g, g.swz(hashed, lane));
209
+
210
+ const contribution = corner_contribution(g, offsets[corner], g.normalize(gradient));
211
+
212
+ total = total === null ? contribution : g.add(total, contribution);
213
+ });
214
+
215
+ // scales the sum to about [-1, 1]
216
+ return g.mul(g.f(42), total);
217
+ }
218
+
219
+ /**
220
+ * The 4D gradient for one corner, from its hashed index.
221
+ *
222
+ * The table this time is the 32 vertices of a hypercube's cell centres, reached the same way: three
223
+ * coordinates from base-7 digits of the index, the fourth from what is left of the L1 norm, and its
224
+ * sign flipping the other three.
225
+ *
226
+ * @param {ParticleGroupBuilder} g
227
+ * @param {*} index
228
+ * @returns {*} a `vec4` gradient, not yet normalised
229
+ */
230
+ function grad4(g, index) {
231
+ // 1/294, 1/49, 1/7: successive base-7 digits of the index
232
+ const digits = g.mul(g.f(1 / 294, 1 / 49, 1 / 7), index);
233
+
234
+ const xyz = g.mad(g.floor(g.mul(g.fract(digits), g.f(7))), g.f(1 / 7), g.f(-1));
235
+
236
+ const w = g.sub(g.f(1.5), g.dot(g.abs(xyz), g.f(1)));
237
+
238
+ const negative = g.op("compare", [w, 0], { op: "lt" });
239
+
240
+ // `2c - 1` over a comparison's 0-or-1: -1 where the coordinate is negative, +1 where it is not
241
+ const sign = g.mad(g.op("compare", [xyz, g.f(0)], { op: "lt" }), g.f(2), g.f(-1));
242
+
243
+ const flipped = g.mad(sign, negative, xyz);
244
+
245
+ return g.vec4(g.x(flipped), g.y(flipped), g.z(flipped), w);
246
+ }
247
+
248
+ /**
249
+ * Simplex noise over four dimensions, in roughly `[-1, 1]`.
250
+ *
251
+ * The fourth dimension is what a moving noise field is made of: hold three of them at a position and
252
+ * walk the fourth with time, and the field changes rather than sliding past.
253
+ *
254
+ * @param {ParticleGroupBuilder} g
255
+ * @param {*} v a `vec4` position
256
+ * @returns {*} a scalar
257
+ */
258
+ function simplex4(g, v) {
259
+ const cell = g.floor(g.add(v, g.dot(v, g.f(F4))));
260
+ const x0 = g.add(g.sub(v, cell), g.dot(cell, g.f(G4)));
261
+
262
+ // The rank of each coordinate among the four, by comparing every pair once. A coordinate's rank
263
+ // is how many of the others it is at least as large as, and the ranks are the order the four
264
+ // steps from the cell's corner are taken in.
265
+ const isX = g.step(g.swz(x0, "yzw"), g.swz(x0, "xxx"));
266
+ const isYZ = g.step(g.swz(x0, "zww"), g.swz(x0, "yyz"));
267
+
268
+ const one_minus = ref => g.sub(g.f(1), ref);
269
+
270
+ // Three comparison results summed, which is a dot product against ones — one instruction and one
271
+ // literal, rather than two adds and three lanes picked out one at a time. The others mix a
272
+ // comparison with its complement and have nothing so tidy to become.
273
+ const rank_x = g.dot(isX, g.f(1));
274
+ const rank_y = g.add(one_minus(g.x(isX)), g.add(g.x(isYZ), g.y(isYZ)));
275
+ const rank_z = g.add(one_minus(g.y(isX)), g.add(one_minus(g.x(isYZ)), g.z(isYZ)));
276
+ const rank_w = g.add(one_minus(g.z(isX)), g.add(one_minus(g.y(isYZ)), one_minus(g.z(isYZ))));
277
+
278
+ const rank = g.vec4(rank_x, rank_y, rank_z, rank_w);
279
+
280
+ // A rank is a whole number in 0..3, and the three steps from the cell's corner are the
281
+ // coordinates whose rank has been passed: the first step takes rank 3, the second 3 and 2, the
282
+ // third all but rank 0. `saturate(rank - k)` is all three of those. `rank - k` never leaves
283
+ // `[-3, 3]`, so clamping to `[0, 1]` is the same function as the one-sided `max` or `min` each of
284
+ // them would otherwise need — and it is one instruction that holds its bounds in the instruction
285
+ // word. This was `min` and `max` precisely to keep two broadcast literals from being alive at the
286
+ // same moment; `saturate` has no literals to be alive, so the reason is gone along with the cost.
287
+ const step_taken = value => g.saturate(value);
288
+
289
+ // The permutation needs one lane of each of the three steps at a time, so it takes them off the
290
+ // ranks directly rather than off three assembled vectors. Those vectors are twelve registers,
291
+ // and holding them through the permutation — the busiest part of the function — is what put this
292
+ // over the register file. Built this way the ranks are all that has to survive it, and the
293
+ // vectors are assembled afterwards for the corner offsets, by which point the hash is done.
294
+ const steps_of = axis => {
295
+ const r = g.swz(rank, axis);
296
+
297
+ return g.vec4(
298
+ step_taken(g.sub(r, g.f(2))),
299
+ step_taken(g.sub(r, g.f(1))),
300
+ step_taken(r),
301
+ g.f(1)
302
+ );
303
+ };
304
+
305
+ const cell289 = mod289(g, cell);
306
+
307
+ // The corner at the cell's own origin hashes on its own; the other four hash together.
308
+ let origin = permute(g, g.w(cell289));
309
+
310
+ origin = permute(g, g.add(origin, g.z(cell289)));
311
+ origin = permute(g, g.add(origin, g.y(cell289)));
312
+ origin = permute(g, g.add(origin, g.x(cell289)));
313
+
314
+ let hashed = permute(g, g.add(g.w(cell289), steps_of("w")));
315
+
316
+ hashed = permute(g, g.add(g.add(hashed, g.z(cell289)), steps_of("z")));
317
+ hashed = permute(g, g.add(g.add(hashed, g.y(cell289)), steps_of("y")));
318
+ hashed = permute(g, g.add(g.add(hashed, g.x(cell289)), steps_of("x")));
319
+
320
+ const step3 = step_taken(rank);
321
+ const step2 = step_taken(g.sub(rank, g.f(1)));
322
+ const step1 = step_taken(g.sub(rank, g.f(2)));
323
+
324
+ // Each of these is one value read once, so only one of them is ever live however they are
325
+ // written down — the order the compiler emits in comes from the graph rather than from the order
326
+ // they were built in. See the note about corners at the top of this file for the part that *is*
327
+ // a register decision.
328
+ const offsets = [
329
+ x0,
330
+ g.add(g.sub(x0, step1), g.f(G4)),
331
+ g.add(g.sub(x0, step2), g.f(G4 * 2)),
332
+ g.add(g.sub(x0, step3), g.f(G4 * 3)),
333
+ g.add(x0, g.f(G4 * 4 - 1)),
334
+ ];
335
+
336
+ const indices = [origin, g.x(hashed), g.y(hashed), g.z(hashed), g.w(hashed)];
337
+
338
+ let total = null;
339
+
340
+ for (let corner = 0; corner < 5; corner++) {
341
+ const gradient = grad4(g, indices[corner]);
342
+
343
+ const contribution = corner_contribution(g, offsets[corner], g.normalize(gradient));
344
+
345
+ total = total === null ? contribution : g.add(total, contribution);
346
+ }
347
+
348
+ return g.mul(g.f(49), total);
349
+ }
350
+
351
+ /**
352
+ * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
353
+ */
354
+ export const PARTICLE_GROUP_SIMPLEX_3 = (() => {
355
+ const g = new ParticleGroupBuilder({
356
+ name: "simplex3",
357
+ label: "Simplex Noise 3D",
358
+ icon: "M12 3l8 4.5v9L12 21l-8-4.5v-9z M12 3v18 M4 7.5l16 9 M20 7.5l-16 9",
359
+ help: "simplex noise at a point in space, about [-1, 1]",
360
+ inputs: [{ name: "p", type: PARTICLE_TYPE_VEC3 }],
361
+ outputs: [{ name: "out", type: PARTICLE_TYPE_F32 }],
362
+ });
363
+
364
+ g.out("out", simplex3(g, g.in.p));
365
+
366
+ return g.build();
367
+ })();
368
+
369
+ /**
370
+ * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
371
+ */
372
+ export const PARTICLE_GROUP_SIMPLEX_4 = (() => {
373
+ const g = new ParticleGroupBuilder({
374
+ name: "simplex4",
375
+ label: "Simplex Noise 4D",
376
+ icon: "M12 3l8 4.5v9L12 21l-8-4.5v-9z M12 3v18 M4 7.5l16 9 M20 7.5l-16 9 M4 7.5l16 0",
377
+ help: "simplex noise at a point in space and time, about [-1, 1]",
378
+ inputs: [{ name: "p", type: PARTICLE_TYPE_VEC4 }],
379
+ outputs: [{ name: "out", type: PARTICLE_TYPE_F32 }],
380
+ });
381
+
382
+ g.out("out", simplex4(g, g.in.p));
383
+
384
+ return g.build();
385
+ })();
386
+
387
+ /**
388
+ * How far apart the samples taken either side of a point are, when differencing the potential.
389
+ *
390
+ * Small enough that the difference is a derivative and large enough that it is not two samples of
391
+ * the same value: simplex noise varies over a unit cell, and a tenth of one is a reasonable step in
392
+ * single precision.
393
+ *
394
+ * @type {number}
395
+ */
396
+ const CURL_EPSILON = 0.1;
397
+
398
+ /**
399
+ * Offsets that turn one noise field into three independent ones.
400
+ *
401
+ * A curl needs a vector potential, and there is one noise function. Sampling it at three points far
402
+ * enough apart that the values are uncorrelated is the standard way to get three fields out of it —
403
+ * the numbers are arbitrary and only have to be large and unalike.
404
+ *
405
+ * @type {number[][]}
406
+ */
407
+ const CURL_FIELD_OFFSETS = [
408
+ [0, 0, 0],
409
+ [31.416, 47.853, 12.793],
410
+ [83.147, 19.264, 65.037],
411
+ ];
412
+
413
+ /**
414
+ * The curl of a noise field, as a vector.
415
+ *
416
+ * A curl is divergence-free — nothing anywhere is a source or a sink — which is what makes it look
417
+ * like a fluid rather than like noise. Particles advected by one swirl and never pile up, and that
418
+ * is the whole reason to spend this many instructions on it.
419
+ *
420
+ * ## What it costs
421
+ *
422
+ * Twelve noise samples. The curl of a three-component potential needs six partial derivatives, each
423
+ * a difference of two samples, and no two of them are of the same field at the same place. There is
424
+ * no loop to put them in and no way to share them, so the group is twelve copies of the noise and
425
+ * the arithmetic between them — well over a thousand instructions, and worth knowing before it is
426
+ * put in an UPDATE graph that runs on every particle every frame.
427
+ *
428
+ * @param {ParticleGroupBuilder} g
429
+ * @param {function(ParticleGroupBuilder, number, number, number, number): *} sample
430
+ * the potential's `field`th component at `p + (dx, dy, dz)`, as a scalar
431
+ * @returns {*} a `vec3`
432
+ */
433
+ function curl_of(g, sample) {
434
+ const e = CURL_EPSILON;
435
+
436
+ /**
437
+ * The centred difference of one field along one axis, *not* divided by the step — every one of
438
+ * the six is over the same step, so the division comes out of the sum at the end as a single
439
+ * scale rather than six multiplies.
440
+ *
441
+ * @param {number} field
442
+ * @param {number} axis 0, 1 or 2
443
+ * @returns {*}
444
+ */
445
+ const difference = (field, axis) => {
446
+ const step = [0, 0, 0];
447
+
448
+ step[axis] = e;
449
+
450
+ const ahead = sample(g, field, step[0], step[1], step[2]);
451
+ const behind = sample(g, field, -step[0], -step[1], -step[2]);
452
+
453
+ return g.sub(ahead, behind);
454
+ };
455
+
456
+ const curl = g.vec3(
457
+ g.sub(difference(2, 1), difference(1, 2)),
458
+ g.sub(difference(0, 2), difference(2, 0)),
459
+ g.sub(difference(1, 0), difference(0, 1))
460
+ );
461
+
462
+ return g.scale(curl, g.f(1 / (2 * e)));
463
+ }
464
+
465
+ /**
466
+ * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
467
+ */
468
+ export const PARTICLE_GROUP_CURL_3 = (() => {
469
+ const g = new ParticleGroupBuilder({
470
+ name: "curl3",
471
+ label: "Curl Noise 3D",
472
+ icon: "M6 15c0-5 5-8 8-5s0 8-3 6-1-6 3-6",
473
+ help: "a divergence-free swirl field — twelve noise samples, so not cheap",
474
+ inputs: [{ name: "p", type: PARTICLE_TYPE_VEC3 }],
475
+ outputs: [{ name: "out", type: PARTICLE_TYPE_VEC3 }],
476
+ });
477
+
478
+ g.out("out", curl_of(g, (builder, field, dx, dy, dz) => {
479
+ const offset = CURL_FIELD_OFFSETS[field];
480
+
481
+ const at = builder.add(builder.in.p, builder.f(offset[0] + dx, offset[1] + dy, offset[2] + dz));
482
+
483
+ return builder.call(PARTICLE_GROUP_SIMPLEX_3, { p: at }).out;
484
+ }));
485
+
486
+ return g.build();
487
+ })();
488
+
489
+ /**
490
+ * The same field, with a fourth coordinate to move it through.
491
+ *
492
+ * Advance `w` with time and the swirl evolves in place instead of drifting past — which is what
493
+ * separates smoke from a conveyor belt of smoke.
494
+ *
495
+ * @type {import("../ParticleNodeGroup.js").ParticleNodeGroup}
496
+ */
497
+ export const PARTICLE_GROUP_CURL_4 = (() => {
498
+ const g = new ParticleGroupBuilder({
499
+ name: "curl4",
500
+ label: "Curl Noise 4D",
501
+ icon: "M6 15c0-5 5-8 8-5s0 8-3 6-1-6 3-6 M18 5v4 M16 7h4",
502
+ help: "a curl field that evolves along w — twelve 4D noise samples, so expensive",
503
+ inputs: [
504
+ { name: "p", type: PARTICLE_TYPE_VEC3 },
505
+ { name: "w", type: PARTICLE_TYPE_F32 },
506
+ ],
507
+ outputs: [{ name: "out", type: PARTICLE_TYPE_VEC3 }],
508
+ });
509
+
510
+ g.out("out", curl_of(g, (builder, field, dx, dy, dz) => {
511
+ const offset = CURL_FIELD_OFFSETS[field];
512
+
513
+ const at = builder.vec4(
514
+ builder.add(builder.x(builder.in.p), builder.f(offset[0] + dx)),
515
+ builder.add(builder.y(builder.in.p), builder.f(offset[1] + dy)),
516
+ builder.add(builder.z(builder.in.p), builder.f(offset[2] + dz)),
517
+ builder.in.w
518
+ );
519
+
520
+ return builder.call(PARTICLE_GROUP_SIMPLEX_4, { p: at }).out;
521
+ }));
522
+
523
+ return g.build();
524
+ })();