@woosh/meep-engine 3.16.0 → 3.17.1

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 (431) hide show
  1. package/README.md +8 -72
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  3. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  4. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  7. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  10. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  11. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  12. package/editor/view/node-graph/NodeGraphView.js +84 -8
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  15. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  16. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  17. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  20. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  22. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  25. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  27. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  28. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  29. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  30. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  31. package/editor/view/particles/effect/particle-editor.css +123 -35
  32. package/package.json +1 -1
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  34. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  35. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  37. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  38. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  39. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  40. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  41. package/src/core/graph/csr/CSRGraph.js +14 -6
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  44. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  47. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  50. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  53. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  55. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  56. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  57. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  59. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  62. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  65. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  68. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  70. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  71. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  74. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  76. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  77. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  78. package/src/engine/animation/AnimationUtils.js +246 -246
  79. package/src/engine/animation/Animations.js +74 -74
  80. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  81. package/src/engine/ecs/EventType.d.ts +0 -1
  82. package/src/engine/ecs/EventType.js +1 -26
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  85. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  86. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  87. package/src/engine/ecs/parent/EntityNode.js +4 -9
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  89. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  93. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  95. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  96. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  97. package/src/engine/ecs/transform/t64_set_rotation_axis_angle.d.ts +25 -0
  98. package/src/engine/ecs/transform/t64_set_rotation_axis_angle.d.ts.map +1 -0
  99. package/src/engine/ecs/transform/t64_set_rotation_axis_angle.js +45 -0
  100. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  101. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  102. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  103. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  104. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  105. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  106. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  107. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/LightSystem.js +4 -8
  109. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/MeshSystem.js +9 -19
  111. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  112. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  113. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  114. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  115. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  116. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  117. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  118. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  119. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  120. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  121. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  122. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  123. package/src/engine/sound/simulation/AcousticSimulator.d.ts +11 -8
  124. package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
  125. package/src/engine/sound/simulation/AcousticSimulator.js +45 -36
  126. package/src/engine/sound/simulation/README.md +2 -2
  127. package/src/engine/sound/simulation/core/OcclusionSolver.d.ts.map +1 -1
  128. package/src/engine/sound/simulation/core/VolumeField.d.ts +9 -9
  129. package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
  130. package/src/engine/sound/simulation/core/VolumeField.js +29 -13
  131. package/src/engine/sound/simulation/core/acoustic_occlusion_solve.d.ts +31 -0
  132. package/src/engine/sound/simulation/core/acoustic_occlusion_solve.d.ts.map +1 -0
  133. package/src/engine/sound/simulation/core/acoustic_occlusion_solve.js +134 -0
  134. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  135. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  136. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  137. package/src/shade/playground/particle_editor/README.md +43 -2
  138. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  139. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  140. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  141. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
  142. package/src/shade/playground/volumetrics_froxel/main.js +6 -1
  143. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  144. package/src/shade/renderer/Renderer.js +13 -1
  145. package/src/shade/renderer/particles/DESIGN.md +104 -19
  146. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  147. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  148. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  149. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  150. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  151. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  152. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  153. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  154. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  155. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  157. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  158. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  159. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  160. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  161. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  162. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  163. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  164. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  165. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  166. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  168. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  169. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  170. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  171. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  172. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  174. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  175. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  176. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  177. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  178. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  179. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  180. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  181. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  182. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  183. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  185. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  186. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  187. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  188. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  189. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  190. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  191. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  192. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  193. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  194. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  196. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  197. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  198. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  199. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  200. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  202. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  203. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/graph_particles.js +105 -262
  205. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  206. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  207. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  208. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  209. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  210. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  211. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  212. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  213. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  214. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  215. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  216. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  217. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  218. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  219. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  220. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  221. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  222. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  223. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  224. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  225. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  226. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  227. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  228. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  229. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  230. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  235. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  236. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  237. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  238. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  239. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  240. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  241. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  242. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  243. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  244. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  245. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  246. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  247. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  248. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  249. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  250. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  251. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  252. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  253. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  254. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  255. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  256. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  257. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  258. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  259. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  261. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  262. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  263. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  264. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  265. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  266. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  267. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  268. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  270. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  271. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  272. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  273. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  274. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  275. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  276. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  277. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  278. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  279. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  280. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  281. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  282. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  283. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  285. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  286. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  287. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  288. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  289. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  291. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  292. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  293. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  294. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  295. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  296. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  297. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  298. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  299. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  300. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  302. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  303. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  305. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  306. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  307. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  308. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  309. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  310. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  311. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  312. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  314. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  315. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  317. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  318. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  320. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  321. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  323. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  324. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  325. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  326. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  327. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  328. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  329. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  330. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  331. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  332. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  333. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  334. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  335. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  336. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  337. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  338. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  339. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  340. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  341. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  342. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  343. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  344. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  345. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  346. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  347. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  348. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  349. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  350. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  351. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  352. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  353. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  354. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  355. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  356. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  357. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  374. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  375. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  376. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  377. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  378. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  379. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  380. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  381. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  382. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  383. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  384. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  385. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  386. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  387. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  388. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  389. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts +7 -0
  390. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts.map +1 -1
  391. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.js +7 -0
  392. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  393. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  394. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  395. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  396. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  397. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  398. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  399. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  400. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  401. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  402. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  403. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  404. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  405. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  406. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  407. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  408. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  409. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  410. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  411. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  412. package/src/engine/sound/simulation/core/OcclusionSolver.js +0 -112
  413. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  414. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  415. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  416. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  417. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  418. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  419. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  420. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  421. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  422. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  423. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +0 -89
  424. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +0 -1
  425. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  426. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  427. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  428. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  429. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  430. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  431. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -0,0 +1,472 @@
1
+ import { hex_to_rgb } from "../../../../src/core/color/hex/hex_to_rgb.js";
2
+ import { rgb_to_hex } from "../../../../src/core/color/hex/rgb_to_hex.js";
3
+ import { clamp01 } from "../../../../src/core/math/clamp01.js";
4
+ import { particle_gradient_sample } from "../../../../src/shade/renderer/particles/graph/particle_ramp.js";
5
+ import View from "../../../../src/view/View.js";
6
+
7
+ /**
8
+ * A colour gradient, edited the way everybody edits one: a strip of the colour it produces, with the
9
+ * stops that make it sitting under it.
10
+ *
11
+ * Drag a stop to move it. Click the lane under the strip where there is no stop and one is added
12
+ * there, in the colour the gradient already is at that point — so adding a stop never changes the
13
+ * gradient, it only gives you somewhere to change it *from*. Middle-click a stop, or press the
14
+ * remove button, and it goes. The swatch and the fields below edit whichever stop is selected.
15
+ *
16
+ * There is nothing clever here and that is deliberate: this control exists in Photoshop, in every
17
+ * game engine and in every vector editor, it works the same way in all of them, and an author should
18
+ * not have to find out how this one is different.
19
+ *
20
+ * ## When an edit is committed
21
+ *
22
+ * On pointer *up*, not on every move. Each commit is one entry on the editor's undo timeline, and a
23
+ * stop dragged across the strip is one edit an author means to undo once — not the ninety pointer
24
+ * events it was made of. The strip redraws continuously while the drag is happening, so it looks
25
+ * live; it is only the timeline that waits.
26
+ */
27
+
28
+ /** How wide the strip is drawn, in CSS pixels. @type {number} */
29
+ const WIDTH = 208;
30
+
31
+ /** How tall the colour strip is. @type {number} */
32
+ const STRIP_HEIGHT = 24;
33
+
34
+ /** How tall the lane of stop markers under it is. @type {number} */
35
+ const LANE_HEIGHT = 13;
36
+
37
+ /** How near a stop marker a press has to be to take hold of it rather than make a new one. */
38
+ const GRAB_RADIUS = 7;
39
+
40
+ /** Squares of the checkerboard drawn behind the strip, so alpha reads as transparency. */
41
+ const CHECKER = 6;
42
+
43
+ /**
44
+ * @param {number} channel `0..1`
45
+ * @returns {number} `0..255`
46
+ */
47
+ function byte_of(channel) {
48
+ return Math.round(clamp01(channel) * 255);
49
+ }
50
+
51
+ export class ParticleGradientEditorView extends View {
52
+ /**
53
+ * The stops being edited. A working copy: it is what is drawn, and it becomes the parameter when
54
+ * an edit is committed.
55
+ * @type {import("../../../../src/shade/renderer/particles/graph/particle_ramp.js").ParticleGradientStop[]}
56
+ */
57
+ #stops = [];
58
+
59
+ /** @type {HTMLCanvasElement} */
60
+ #canvas;
61
+
62
+ /** @type {CanvasRenderingContext2D|null} */
63
+ #context = null;
64
+
65
+ /** @type {number} which stop the swatch and the fields below are about */
66
+ #selected = 0;
67
+
68
+ /** @type {boolean} */
69
+ #dragging = false;
70
+
71
+ /** @type {function(Array):void} */
72
+ #write;
73
+
74
+ /** @type {function():Array} */
75
+ #read;
76
+
77
+ /** @type {boolean} whether this gradient's alpha is read by the node it belongs to */
78
+ #has_alpha;
79
+
80
+ /** @type {HTMLInputElement} */
81
+ #swatch;
82
+
83
+ /** @type {HTMLInputElement} */
84
+ #alpha;
85
+
86
+ /** @type {HTMLInputElement} */
87
+ #position;
88
+
89
+ /**
90
+ * @param {object} options
91
+ * @param {function():Array} options.read the parameter's current value
92
+ * @param {function(Array):void} options.write commit a new value
93
+ * @param {boolean} [options.alpha] whether the node reads the alpha channel; an RGB gradient
94
+ * still *stores* one — see {@link ./particle_ramp.js} — but does not offer it for editing
95
+ */
96
+ constructor({ read, write, alpha = true }) {
97
+ super();
98
+
99
+ this.#read = read;
100
+ this.#write = write;
101
+ this.#has_alpha = alpha;
102
+
103
+ this.el = document.createElement('div');
104
+
105
+ this.addClass('ui-particle-gradient');
106
+
107
+ const canvas = this.#canvas = document.createElement('canvas');
108
+
109
+ canvas.width = WIDTH;
110
+ canvas.height = STRIP_HEIGHT + LANE_HEIGHT;
111
+ canvas.classList.add('strip');
112
+
113
+ this.el.appendChild(canvas);
114
+
115
+ const fields = document.createElement('div');
116
+
117
+ fields.classList.add('stop-fields');
118
+
119
+ this.el.appendChild(fields);
120
+
121
+ this.#swatch = field(fields, 'color', 'swatch');
122
+ this.#alpha = field(fields, 'number', 'alpha');
123
+ this.#position = field(fields, 'number', 'position');
124
+
125
+ for (const input of [this.#alpha, this.#position]) {
126
+ input.setAttribute('min', '0');
127
+ input.setAttribute('max', '1');
128
+ input.setAttribute('step', 'any');
129
+ }
130
+
131
+ this.#alpha.hidden = !alpha;
132
+
133
+ const remove = document.createElement('button');
134
+
135
+ remove.type = 'button';
136
+ remove.classList.add('remove');
137
+ remove.textContent = '−';
138
+ remove.title = "Remove the selected stop (or middle-click it)";
139
+
140
+ fields.appendChild(remove);
141
+
142
+ this.#swatch.title = "The selected stop's colour";
143
+ this.#alpha.title = "The selected stop's alpha";
144
+ this.#position.title = "Where the selected stop sits along t";
145
+
146
+ canvas.addEventListener('pointerdown', event => this.#press(event));
147
+ canvas.addEventListener('pointermove', event => this.#move(event));
148
+ canvas.addEventListener('pointerup', event => this.#release(event));
149
+ canvas.addEventListener('pointercancel', event => this.#release(event));
150
+
151
+ this.#swatch.addEventListener('change', () => this.#editSelected(stop => {
152
+ const rgb = hex_to_rgb(this.#swatch.value);
153
+
154
+ stop.color = [rgb.r / 255, rgb.g / 255, rgb.b / 255, stop.color[3] ?? 1];
155
+ }));
156
+
157
+ this.#alpha.addEventListener('change', () => this.#editSelected(stop => {
158
+ const [r = 0, g = 0, b = 0, a = 1] = stop.color;
159
+
160
+ stop.color = [r, g, b, clamp01(number_in(this.#alpha, a))];
161
+ }));
162
+
163
+ this.#position.addEventListener('change', () => this.#editSelected(stop => {
164
+ stop.position = number_in(this.#position, stop.position);
165
+ }));
166
+
167
+ remove.addEventListener('click', () => this.#removeSelected());
168
+
169
+ this.on.linked.add(() => {
170
+ if (this.#context === null) {
171
+ this.#context = canvas.getContext('2d');
172
+ }
173
+
174
+ this.refresh();
175
+ });
176
+
177
+ this.refresh();
178
+ }
179
+
180
+ /**
181
+ * Hand the working stops to the parameter, as a copy.
182
+ *
183
+ * A copy because this view goes on mutating its own array — the next drag writes straight into
184
+ * it — and what the node holds has to be what was committed rather than whatever this is doing
185
+ * next. It is also what makes each commit a distinct value, which is how the node knows anything
186
+ * changed at all.
187
+ */
188
+ #commit() {
189
+ this.#write(structuredClone(this.#stops));
190
+ }
191
+
192
+ /**
193
+ * Re-read the parameter and redraw. Called when the value changed somewhere else — an undo, the
194
+ * code layer, a script.
195
+ */
196
+ refresh() {
197
+ const value = this.#read();
198
+
199
+ this.#stops = Array.isArray(value)
200
+ ? value.map(stop => ({ position: stop.position, color: (stop.color ?? []).slice() }))
201
+ : [];
202
+
203
+ this.#selected = Math.min(this.#selected, Math.max(0, this.#stops.length - 1));
204
+
205
+ this.#writeFields();
206
+ this.render();
207
+ }
208
+
209
+ /**
210
+ * The stops in the order they are drawn and committed in, paired with where they were.
211
+ *
212
+ * Sorted for drawing and for the rollout, but the *selection* is an index into the unsorted
213
+ * array — so dragging a stop past its neighbour does not hand the selection to the neighbour,
214
+ * which is what makes a stop draggable all the way across the strip in one gesture.
215
+ *
216
+ * @returns {Array<{stop: object, index: number}>}
217
+ */
218
+ #ordered() {
219
+ return this.#stops
220
+ .map((stop, index) => ({ stop, index }))
221
+ .sort((a, b) => a.stop.position - b.stop.position);
222
+ }
223
+
224
+ /**
225
+ * @param {PointerEvent} event
226
+ * @returns {number} where along the strip the pointer is, `0..1`
227
+ */
228
+ #positionOf(event) {
229
+ const rect = this.#canvas.getBoundingClientRect();
230
+
231
+ return clamp01((event.clientX - rect.left) / rect.width);
232
+ }
233
+
234
+ /**
235
+ * @param {PointerEvent} event
236
+ * @returns {number} the index of the stop under the pointer, or -1
237
+ */
238
+ #stopUnder(event) {
239
+ const rect = this.#canvas.getBoundingClientRect();
240
+
241
+ const x = event.clientX - rect.left;
242
+
243
+ // in CSS pixels, so the grab radius is the same however far the graph is zoomed out
244
+ const radius = GRAB_RADIUS * (rect.width / WIDTH);
245
+
246
+ let nearest = -1;
247
+ let distance = Infinity;
248
+
249
+ this.#stops.forEach((stop, index) => {
250
+ const away = Math.abs(stop.position * rect.width - x);
251
+
252
+ if (away <= radius && away < distance) {
253
+ nearest = index;
254
+ distance = away;
255
+ }
256
+ });
257
+
258
+ return nearest;
259
+ }
260
+
261
+ /**
262
+ * @param {PointerEvent} event
263
+ */
264
+ #press(event) {
265
+ const stop = this.#stopUnder(event);
266
+
267
+ if (event.button === 1) {
268
+ // the power-user path to the remove button beside the fields
269
+ if (stop !== -1) {
270
+ this.#selected = stop;
271
+ this.#removeSelected();
272
+ }
273
+
274
+ event.preventDefault();
275
+ return;
276
+ }
277
+
278
+ if (event.button !== 0) {
279
+ return;
280
+ }
281
+
282
+ if (stop !== -1) {
283
+ this.#selected = stop;
284
+ } else {
285
+ const position = this.#positionOf(event);
286
+
287
+ // in the colour the gradient already is here, so adding a stop is not itself an edit to
288
+ // the gradient — it is somewhere to make one from
289
+ this.#stops.push({
290
+ position,
291
+ color: particle_gradient_sample(this.#stops, position),
292
+ });
293
+
294
+ this.#selected = this.#stops.length - 1;
295
+
296
+ this.#commit();
297
+ }
298
+
299
+ this.#dragging = true;
300
+
301
+ if (typeof this.#canvas.setPointerCapture === "function") {
302
+ this.#canvas.setPointerCapture(event.pointerId);
303
+ }
304
+
305
+ this.#writeFields();
306
+ this.render();
307
+ }
308
+
309
+ /**
310
+ * @param {PointerEvent} event
311
+ */
312
+ #move(event) {
313
+ if (!this.#dragging) {
314
+ return;
315
+ }
316
+
317
+ this.#stops[this.#selected].position = this.#positionOf(event);
318
+
319
+ this.#writeFields();
320
+ this.render();
321
+ }
322
+
323
+ /**
324
+ * @param {PointerEvent} event
325
+ */
326
+ #release(event) {
327
+ if (!this.#dragging) {
328
+ return;
329
+ }
330
+
331
+ this.#dragging = false;
332
+
333
+ if (typeof this.#canvas.releasePointerCapture === "function" && this.#canvas.hasPointerCapture?.(event.pointerId)) {
334
+ this.#canvas.releasePointerCapture(event.pointerId);
335
+ }
336
+
337
+ // one entry on the timeline for the whole drag; see the note at the top of this file
338
+ this.#commit();
339
+ }
340
+
341
+ /**
342
+ * @param {function(object):void} edit applied to the selected stop
343
+ */
344
+ #editSelected(edit) {
345
+ const stop = this.#stops[this.#selected];
346
+
347
+ if (stop === undefined) {
348
+ return;
349
+ }
350
+
351
+ edit(stop);
352
+
353
+ this.#commit();
354
+ this.#writeFields();
355
+ this.render();
356
+ }
357
+
358
+ #removeSelected() {
359
+ // A gradient with no stops has no colour to be, and the validator says so. Rather than let
360
+ // an author reach that state by clicking one button too many, the last stop stays.
361
+ if (this.#stops.length <= 1) {
362
+ return;
363
+ }
364
+
365
+ this.#stops.splice(this.#selected, 1);
366
+
367
+ this.#selected = Math.min(this.#selected, this.#stops.length - 1);
368
+
369
+ this.#commit();
370
+ this.#writeFields();
371
+ this.render();
372
+ }
373
+
374
+ /**
375
+ * Put the selected stop into the fields under the strip.
376
+ */
377
+ #writeFields() {
378
+ const stop = this.#stops[this.#selected];
379
+
380
+ const colour = stop === undefined ? [0, 0, 0, 1] : stop.color;
381
+
382
+ this.#swatch.value = `#${rgb_to_hex(byte_of(colour[0]), byte_of(colour[1]), byte_of(colour[2]))}`;
383
+ this.#alpha.value = String(colour[3] ?? 1);
384
+ this.#position.value = String(stop === undefined ? 0 : stop.position);
385
+ }
386
+
387
+ render() {
388
+ const context = this.#context;
389
+
390
+ if (context === null) {
391
+ return;
392
+ }
393
+
394
+ const canvas = this.#canvas;
395
+
396
+ context.clearRect(0, 0, canvas.width, canvas.height);
397
+
398
+ // A checkerboard behind the strip, so that a stop's alpha is visible as transparency rather
399
+ // than as a colour that happens to look like the panel behind it.
400
+ if (this.#has_alpha) {
401
+ for (let x = 0; x < canvas.width; x += CHECKER) {
402
+ for (let y = 0; y < STRIP_HEIGHT; y += CHECKER) {
403
+ context.fillStyle = ((x + y) / CHECKER) % 2 === 0 ? '#4c4f54' : '#6a6e74';
404
+ context.fillRect(x, y, CHECKER, CHECKER);
405
+ }
406
+ }
407
+ }
408
+
409
+ // The strip is drawn by sampling the same function the node compiles to, so what is on
410
+ // screen is what the effect will do — including a hard edge where two stops share a place.
411
+ for (let x = 0; x < canvas.width; x++) {
412
+ const colour = particle_gradient_sample(this.#stops, x / (canvas.width - 1));
413
+
414
+ const alpha = this.#has_alpha ? clamp01(colour[3] ?? 1) : 1;
415
+
416
+ context.fillStyle = `rgba(${byte_of(colour[0])}, ${byte_of(colour[1])}, ${byte_of(colour[2])}, ${alpha})`;
417
+ context.fillRect(x, 0, 1, STRIP_HEIGHT);
418
+ }
419
+
420
+ for (const { stop, index } of this.#ordered()) {
421
+ const x = clamp01(stop.position) * canvas.width;
422
+ const selected = index === this.#selected;
423
+
424
+ context.beginPath();
425
+ context.moveTo(x, STRIP_HEIGHT + 1);
426
+ context.lineTo(x - 5, STRIP_HEIGHT + LANE_HEIGHT - 1);
427
+ context.lineTo(x + 5, STRIP_HEIGHT + LANE_HEIGHT - 1);
428
+ context.closePath();
429
+
430
+ // the marker carries the stop's own colour, so the lane reads as the stops rather than
431
+ // as a row of identical arrows
432
+ context.fillStyle = `rgb(${byte_of(stop.color[0])}, ${byte_of(stop.color[1])}, ${byte_of(stop.color[2])})`;
433
+ context.fill();
434
+
435
+ context.lineWidth = selected ? 2 : 1;
436
+ context.strokeStyle = selected ? '#ffd35c' : '#20242a';
437
+ context.stroke();
438
+ }
439
+ }
440
+ }
441
+
442
+ /**
443
+ * @param {HTMLElement} parent
444
+ * @param {string} type
445
+ * @param {string} className
446
+ * @returns {HTMLInputElement}
447
+ */
448
+ function field(parent, type, className) {
449
+ const input = document.createElement('input');
450
+
451
+ input.setAttribute('type', type);
452
+ input.classList.add(className);
453
+
454
+ parent.appendChild(input);
455
+
456
+ return input;
457
+ }
458
+
459
+ /**
460
+ * What a number field holds, or `fallback` where it holds nothing a number can be made of — a
461
+ * half-typed value should snap back rather than become zero.
462
+ *
463
+ * @param {HTMLInputElement} input
464
+ * @param {number} fallback
465
+ * @returns {number}
466
+ */
467
+ function number_in(input, fallback) {
468
+ const parsed = Number.parseFloat(input.value);
469
+
470
+ return Number.isFinite(parsed) ? parsed : fallback;
471
+ }
472
+
@@ -131,6 +131,11 @@ export class ParticleGraphEditorView extends View {
131
131
  * Everything is listed, whichever phase it belongs to, because a problem in the phase an author
132
132
  * is not looking at is exactly the one they would otherwise hunt for. Nodes carrying a problem
133
133
  * are marked in the graph as well.
134
+ *
135
+ * Loudest first, so a strip too short to show everything shows what matters. What is left over
136
+ * at the bottom is the notes — a node nothing reads, most of all — and a graph with nothing but
137
+ * those still says it has no problems, because it has none: they are things worth knowing about
138
+ * an effect that compiles.
134
139
  */
135
140
  refreshProblems(): void;
136
141
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ParticleGraphEditorView.d.ts","sourceRoot":"","sources":["../../../../../editor/view/particles/effect/ParticleGraphEditorView.js"],"names":[],"mappings":"AA4KA;IAgFI;;;;;OAKG;IACH;QAJyG,MAAM,EAApG,OAAO,qDAAqD,EAAE,sBAAsB;QAC1D,eAAe;OAsDnD;IAhDG,mBAAuC;IAIvC;;OAEG;IACH,QAFU,OAAO,qDAAqD,EAAE,sBAAsB,CAE1E;IAEpB;;OAEG;IACH,2CAAsC;IAsX1C;;;;;;;;;;;;;;;OAeG;IACH,kBAHa,QAAQ,eAAa,IAAI,CAAC,CAwEtC;IAED;;;;;;;;OAQG;IACH,oBAFa,QAAQ,cAAc,CAAC,CAgCnC;IA6ID;;;;;;;;;;;;;OAaG;IACH,iBAHW,OAAO,qEAAqE,EAAE,iBAAiB,GAC7F,IAAI,CAsDhB;IAED;;;OAGG;IACH,cAFa,IAAI,CAIhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,IAAI,CAWhB;IAED;;;;OAIG;IACH,cAFa,OAAO,qEAAqE,EAAE,iBAAiB,EAAE,CAI7G;IAED;;;;;;;;;;OAUG;IACH,gBAuCC;IAwBD;;;;;;;OAOG;IACH,wDAHW,MAAM,GACJ,qBAAqB,CAiBjC;IAiED;;;;OAIG;IACH,gBAFa,mBAAmB,CAQ/B;IAED;;OAEG;IACH,eAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,aAFW,MAAM,QAmBhB;IAoGD;;;;;;;;;OASG;IACH,mBAJW,OAAO,qEAAqE,EAAE,iBAAiB,SAC/F,MAAM,GACJ,IAAI,CAqBhB;IAED;;;;;;OAMG;IACH,wBAgCC;IAgDD;;;;;OAKG;IACH,qBAHW,MAAM,WACN,MAAM,QA2BhB;IAED;;;;;OAKG;IACH,cAFa,OAAO,6CAA6C,EAAE,uBAAuB,GAAC,IAAI,CAI9F;IAED,eAuBC;;CAgCJ;iBA99CgB,8BAA8B;gCA/Bf,sDAAsD;oCAiClD,yCAAyC"}
1
+ {"version":3,"file":"ParticleGraphEditorView.d.ts","sourceRoot":"","sources":["../../../../../editor/view/particles/effect/ParticleGraphEditorView.js"],"names":[],"mappings":"AA0LA;IA6FI;;;;;OAKG;IACH;QAJyG,MAAM,EAApG,OAAO,qDAAqD,EAAE,sBAAsB;QAC1D,eAAe;OAsDnD;IAhDG,mBAAuC;IAIvC;;OAEG;IACH,QAFU,OAAO,qDAAqD,EAAE,sBAAsB,CAE1E;IAEpB;;OAEG;IACH,2CAAsC;IA4X1C;;;;;;;;;;;;;;;OAeG;IACH,kBAHa,QAAQ,eAAa,IAAI,CAAC,CAwEtC;IAED;;;;;;;;OAQG;IACH,oBAFa,QAAQ,cAAc,CAAC,CAgCnC;IA+ID;;;;;;;;;;;;;OAaG;IACH,iBAHW,OAAO,qEAAqE,EAAE,iBAAiB,GAC7F,IAAI,CAsDhB;IAED;;;OAGG;IACH,cAFa,IAAI,CAIhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,IAAI,CAWhB;IAED;;;;OAIG;IACH,cAFa,OAAO,qEAAqE,EAAE,iBAAiB,EAAE,CAI7G;IAED;;;;;;;;;;OAUG;IACH,gBAuCC;IAwBD;;;;;;;OAOG;IACH,wDAHW,MAAM,GACJ,qBAAqB,CAiBjC;IAiED;;;;OAIG;IACH,gBAFa,mBAAmB,CAQ/B;IAED;;OAEG;IACH,eAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,aAFW,MAAM,QAmBhB;IAoGD;;;;;;;;;OASG;IACH,mBAJW,OAAO,qEAAqE,EAAE,iBAAiB,SAC/F,MAAM,GACJ,IAAI,CAqBhB;IAED;;;;;;;;;;;OAWG;IACH,wBAoCC;IAoDD;;;;;OAKG;IACH,qBAHW,MAAM,WACN,MAAM,QA2BhB;IAED;;;;;OAKG;IACH,cAFa,OAAO,6CAA6C,EAAE,uBAAuB,GAAC,IAAI,CAI9F;IAED,eAuBC;;CAgCJ;iBAtgDgB,8BAA8B;gCAvCf,sDAAsD;oCA0ClD,yCAAyC"}
@@ -3,13 +3,21 @@ import { PortDirection } from "../../../../src/core/model/node-graph/node/PortDi
3
3
  import { ActionProcessor } from "../../../../src/core/process/undo/ActionProcessor.js";
4
4
  import { infer_particle_graph_types } from "../../../../src/shade/renderer/particles/graph/infer_particle_graph_types.js";
5
5
  import { particle_group_from_nodes } from "../../../../src/shade/renderer/particles/graph/make_particle_group.js";
6
+ import {
7
+ PARTICLE_GRAPH_ERROR,
8
+ PARTICLE_GRAPH_INFO,
9
+ PARTICLE_GRAPH_SEVERITY_RANK,
10
+ PARTICLE_GRAPH_WARNING,
11
+ } from "../../../../src/shade/renderer/particles/graph/particle_graph_diagnostic.js";
6
12
  import {
7
13
  particle_group_descriptions,
8
14
  } from "../../../../src/shade/renderer/particles/graph/particle_group_json.js";
9
15
  import {
16
+ is_particle_group_boundary,
10
17
  is_particle_group_description,
11
18
  } from "../../../../src/shade/renderer/particles/graph/ParticleNodeGroup.js";
12
19
  import {
20
+ clone_particle_parameters,
13
21
  particle_node_default_parameters,
14
22
  particle_node_parameters,
15
23
  } from "../../../../src/shade/renderer/particles/graph/particle_node_parameters.js";
@@ -33,6 +41,7 @@ import LabelView from "../../../../src/view/common/LabelView.js";
33
41
  import EmptyView from "../../../../src/view/elements/EmptyView.js";
34
42
  import View from "../../../../src/view/View.js";
35
43
  import { NodeGraphCamera } from "../../node-graph/NodeGraphCamera.js";
44
+ import { NodeGraphClipboard } from "../../node-graph/NodeGraphClipboard.js";
36
45
  import { NodeGraphEditorView } from "../../node-graph/NodeGraphEditorView.js";
37
46
  import { NodeCreateAction } from "../../node-graph/actions/NodeCreateAction.js";
38
47
  import { NodeDeleteAction } from "../../node-graph/actions/NodeDeleteAction.js";
@@ -48,8 +57,9 @@ import { ParticleNodeParametersView } from "./ParticleNodeParametersView.js";
48
57
  *
49
58
  * Built *with* the node-graph editor rather than in it. Everything particle-specific arrives
50
59
  * through the seams the base offers — the node library the canvas's add-node menu offers, a
51
- * parameter-view factory that draws vector, enum, attribute and swizzle controls, and a problems
52
- * list fed by the document's own compile. The base is left knowing nothing about particles.
60
+ * parameter-view factory that draws vector, enum, attribute and swizzle controls, the one rule the
61
+ * clipboard needs (a group's boundary blocks are not something there can be a second of), and a
62
+ * problems list fed by the document's own compile. The base is left knowing nothing about particles.
53
63
  *
54
64
  * ## Adding a node
55
65
  *
@@ -79,8 +89,8 @@ import { ParticleNodeParametersView } from "./ParticleNodeParametersView.js";
79
89
  * ## What is undoable
80
90
  *
81
91
  * Everything that touches a graph, through the {@link ActionProcessor} the host supplies. Adding a
82
- * node from the menu, wiring, moving, deleting, and every parameter edit inside a node all land
83
- * on one timeline, so a host editor's global undo covers particle authoring without knowing it
92
+ * node from the menu, wiring, moving, deleting, pasting, and every parameter edit inside a node all
93
+ * land on one timeline, so a host editor's global undo covers particle authoring without knowing it
84
94
  * exists.
85
95
  */
86
96
 
@@ -127,13 +137,17 @@ const PARTICLE_TYPE_COLORS = new Map([
127
137
  const PALETTE_CATEGORIES = [
128
138
  { name: "sources", types: ["const", "color", "builtin", "attribute", "random", "curve"] },
129
139
  { name: "output", types: ["setAttribute", "kill"] },
130
- { name: "arithmetic", types: ["add", "sub", "mul", "div", "mad", "scale", "min", "max", "pow", "step", "mix", "clamp", "smoothstep", "remap", "oneMinus"] },
131
- { name: "math", types: ["neg", "abs", "floor", "fract", "round", "ceil", "sqrt", "sin", "cos", "exp", "log"] },
140
+ // The three that are authored data rather than wiring, and are edited as a picture: a gradient
141
+ // strip and a curve in the unit square. Their own heading, because "a colour ramp" is a thing an
142
+ // author goes looking for by name and would not think to look for under `sources`.
143
+ { name: "ramps", types: ["gradientRGBA", "gradientRGB", "curveLiteral"] },
144
+ { name: "arithmetic", types: ["add", "sub", "mul", "div", "mad", "scale", "min", "max", "pow", "step", "mix", "clamp", "saturate", "smoothstep", "remap", "oneMinus"] },
145
+ { name: "math", types: ["neg", "abs", "floor", "fract", "round", "ceil", "sqrt", "rsqrt", "sin", "cos", "tan", "asin", "acos", "atan2", "exp", "log"] },
132
146
  { name: "vector", types: ["vec2", "vec3", "vec4", "decompose2", "decompose3", "decompose4", "swizzle", "dot", "cross", "length", "normalize", "distance", "angleBetween2", "angleBetween3"] },
133
147
  { name: "logic", types: ["compare", "select"] },
134
148
  { name: "rotation", types: ["quatFromAxisAngle", "quatLookRotation", "quatRotate"] },
135
149
  { name: "sampling", types: ["sampleSphereSurface", "sampleSphereVolume"] },
136
- { name: "noise", types: ["simplex3", "simplex4", "curl3", "curl4"] },
150
+ { name: "noise", types: ["simplex3", "simplex4", "curl3", "curl4", "srdnoise3", "psrdnoise3"] },
137
151
  ];
138
152
 
139
153
  /**
@@ -221,6 +235,19 @@ export class ParticleGraphEditorView extends View {
221
235
  */
222
236
  #open_groups = [];
223
237
 
238
+ /**
239
+ * One clipboard for every canvas this view builds — both phases, and the body of every group
240
+ * opened inside them.
241
+ *
242
+ * `Ctrl+C` in INIT and `Ctrl+V` in UPDATE is the same gesture an author expects it to be, and a
243
+ * clipboard per canvas would make it two. It belongs to this view rather than to the module,
244
+ * because what it holds is node types this document knows — a group an author made here has no
245
+ * meaning in another document's graph.
246
+ *
247
+ * @type {NodeGraphClipboard}
248
+ */
249
+ #clipboard = new NodeGraphClipboard();
250
+
224
251
  /**
225
252
  * Phases whose camera has not yet been pointed at their contents. A graph that has just been
226
253
  * arranged for the first time is somewhere in particular, and it is not the origin.
@@ -438,6 +465,12 @@ export class ParticleGraphEditorView extends View {
438
465
  camera: new NodeGraphCamera(),
439
466
  actionProcessor: this.actionProcessor,
440
467
  readOnly,
468
+ clipboard: this.#clipboard,
469
+ // A group body's input and output blocks are its edge, not its contents: there is one of
470
+ // each, the group holds them by reference, and a second would survive flattening and
471
+ // reach the compiler. So they are left out of a copy rather than the copy being refused
472
+ // — an author who dragged a box around a whole group body wants what is inside it.
473
+ copyable: node => !is_particle_group_boundary(node),
441
474
  library: {
442
475
  // The menu heads each *run* of one category, so the order it is handed decides the
443
476
  // order it shows; sorting here keeps that decision on the particle side of the seam.
@@ -815,7 +848,9 @@ export class ParticleGraphEditorView extends View {
815
848
  inner.description,
816
849
  (placed?.x ?? 0) + (at === undefined ? 0 : at.x - origin.x),
817
850
  (placed?.y ?? 0) + (at === undefined ? 0 : at.y - origin.y),
818
- clone_parameters(inner.parameters)
851
+ // deeply: an ungrouped node must not share so much as a gradient stop with the group
852
+ // definition it came out of
853
+ clone_particle_parameters(inner.parameters)
819
854
  ));
820
855
  }
821
856
 
@@ -1354,6 +1389,11 @@ export class ParticleGraphEditorView extends View {
1354
1389
  * Everything is listed, whichever phase it belongs to, because a problem in the phase an author
1355
1390
  * is not looking at is exactly the one they would otherwise hunt for. Nodes carrying a problem
1356
1391
  * are marked in the graph as well.
1392
+ *
1393
+ * Loudest first, so a strip too short to show everything shows what matters. What is left over
1394
+ * at the bottom is the notes — a node nothing reads, most of all — and a graph with nothing but
1395
+ * those still says it has no problems, because it has none: they are things worth knowing about
1396
+ * an effect that compiles.
1357
1397
  */
1358
1398
  refreshProblems() {
1359
1399
  // The graph's own problems, not the running program's: with the code layer authoritative the
@@ -1363,9 +1403,13 @@ export class ParticleGraphEditorView extends View {
1363
1403
 
1364
1404
  this.#diagnostics = diagnostics;
1365
1405
 
1406
+ // stable within a level, so the order the checks found them in survives
1407
+ diagnostics.sort((a, b) =>
1408
+ PARTICLE_GRAPH_SEVERITY_RANK[b.severity] - PARTICLE_GRAPH_SEVERITY_RANK[a.severity]);
1409
+
1366
1410
  this.#problems.removeAllChildren();
1367
1411
 
1368
- if (diagnostics.length === 0) {
1412
+ if (!diagnostics.some(diagnostic => diagnostic.severity !== PARTICLE_GRAPH_INFO)) {
1369
1413
  this.#problems.addChild(new LabelView("No problems.", { classList: ['none'] }));
1370
1414
  }
1371
1415
 
@@ -1425,14 +1469,18 @@ export class ParticleGraphEditorView extends View {
1425
1469
  continue;
1426
1470
  }
1427
1471
 
1428
- // an error outranks a warning on the same node
1429
- if (diagnostic.severity === "error" || severity === undefined) {
1472
+ // a node wears the loudest thing said about it: an error over a warning, either over a note
1473
+ if (
1474
+ severity === undefined
1475
+ || PARTICLE_GRAPH_SEVERITY_RANK[diagnostic.severity] > PARTICLE_GRAPH_SEVERITY_RANK[severity]
1476
+ ) {
1430
1477
  severity = diagnostic.severity;
1431
1478
  }
1432
1479
  }
1433
1480
 
1434
- nodeView.setClass('has-error', severity === "error");
1435
- nodeView.setClass('has-warning', severity === "warning");
1481
+ nodeView.setClass('has-error', severity === PARTICLE_GRAPH_ERROR);
1482
+ nodeView.setClass('has-warning', severity === PARTICLE_GRAPH_WARNING);
1483
+ nodeView.setClass('has-info', severity === PARTICLE_GRAPH_INFO);
1436
1484
  }
1437
1485
 
1438
1486
  /**
@@ -1596,24 +1644,6 @@ function body_origin(group) {
1596
1644
  return Number.isFinite(x) ? { x, y } : { x: 0, y: 0 };
1597
1645
  }
1598
1646
 
1599
- /**
1600
- * A node's parameters, deep enough that the copy and the original cannot write through to each
1601
- * other — an ungrouped node must not share an array with the group's definition.
1602
- *
1603
- * @param {object} parameters
1604
- * @returns {object}
1605
- */
1606
- function clone_parameters(parameters) {
1607
- const result = {};
1608
-
1609
- for (const key of Object.keys(parameters)) {
1610
- const value = parameters[key];
1611
-
1612
- result[key] = Array.isArray(value) ? value.slice() : value;
1613
- }
1614
-
1615
- return result;
1616
- }
1617
1647
 
1618
1648
  /**
1619
1649
  * @param {string} type node type name