@woosh/meep-engine 3.19.0 → 3.20.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 (123) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/editor/view/particles/effect/ParticleGraphEditorView.js +2 -2
  3. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  4. package/editor/view/particles/effect/ParticleNodeParametersView.js +7 -48
  5. package/package.json +1 -1
  6. package/src/core/collection/array/array_group_by.d.ts.map +1 -1
  7. package/src/core/collection/array/array_group_by.js +2 -11
  8. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  9. package/src/core/geom/3d/shape/HeightMapShape3D.js +4 -9
  10. package/src/core/model/object/optional_clone.d.ts +7 -0
  11. package/src/core/model/object/optional_clone.d.ts.map +1 -0
  12. package/src/core/model/object/optional_equality.d.ts +10 -0
  13. package/src/core/model/object/optional_equality.d.ts.map +1 -0
  14. package/src/{shade/descriptor/util → core/model/object}/optional_equality.js +2 -2
  15. package/src/core/model/object/optional_hash.d.ts +9 -0
  16. package/src/core/model/object/optional_hash.d.ts.map +1 -0
  17. package/src/{shade/descriptor/util → core/model/object}/optional_hash.js +3 -3
  18. package/src/engine/asset/Asset.d.ts +1 -1
  19. package/src/engine/asset/Asset.d.ts.map +1 -1
  20. package/src/engine/asset/Asset.js +3 -1
  21. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts.map +1 -1
  22. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +9 -10
  23. package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts.map +1 -1
  24. package/src/engine/graphics/ecs/particles/ParticleEffect.js +6 -22
  25. package/src/shade/descriptor/binding/BindGroupLayoutDescriptor.js +1 -1
  26. package/src/shade/descriptor/binding/BindGroupLayoutEntry.js +3 -3
  27. package/src/shade/descriptor/pipeline/PipelineDescriptorBase.js +1 -1
  28. package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +1 -1
  29. package/src/shade/descriptor/pipeline/render/ColorTargetState.js +3 -3
  30. package/src/shade/descriptor/pipeline/render/FragmentState.js +1 -1
  31. package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.js +3 -3
  32. package/src/shade/playground/particle_editor/README.md +1 -1
  33. package/src/shade/playground/particle_system/particle_prototype.d.ts +7 -9
  34. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  35. package/src/shade/playground/particle_system/particle_prototype.js +7 -9
  36. package/src/shade/playground/ssr_motion/README.md +22 -0
  37. package/src/shade/playground/ssr_motion/index.html +18 -0
  38. package/src/shade/playground/ssr_motion/main.d.ts +2 -0
  39. package/src/shade/playground/ssr_motion/main.d.ts.map +1 -0
  40. package/src/shade/playground/ssr_motion/main.js +149 -0
  41. package/src/shade/playground/ssr_variance/index.html +1 -0
  42. package/src/shade/renderer/geometry/Geometry.d.ts.map +1 -1
  43. package/src/shade/renderer/geometry/Geometry.js +4 -7
  44. package/src/shade/renderer/material/StandardShadeMaterial.js +2 -2
  45. package/src/shade/renderer/particles/DESIGN.md +9 -11
  46. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  47. package/src/shade/renderer/particles/GPUParticleSystem.js +3 -1
  48. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +1 -1
  49. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +1 -1
  50. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  51. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +9 -15
  52. package/src/shade/renderer/particles/graph/particle_group_library.d.ts.map +1 -1
  53. package/src/shade/renderer/particles/graph/particle_group_library.js +8 -3
  54. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +0 -2
  55. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  56. package/src/shade/renderer/particles/graph/particle_node_parameters.js +0 -9
  57. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  58. package/src/shade/renderer/particles/graph/particle_node_presentation.js +0 -10
  59. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +2 -8
  60. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -1
  61. package/src/shade/renderer/particles/graph/particle_ramp.js +2 -8
  62. package/src/shade/renderer/particles/graph/validate_particle_graph.js +0 -6
  63. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  64. package/src/shade/renderer/particles/graph_particles_avboit.js +13 -4
  65. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +1 -12
  66. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  67. package/src/shade/renderer/particles/isa/InstructionStream.js +1 -12
  68. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +54 -60
  69. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  70. package/src/shade/renderer/particles/isa/ParticleVMISA.js +9 -17
  71. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +0 -1
  72. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  73. package/src/shade/renderer/particles/isa/particle_assembly.js +2 -16
  74. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +1 -1
  75. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +1 -1
  76. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +0 -14
  77. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +3 -17
  78. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  79. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +15 -36
  80. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  81. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +1 -5
  82. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  83. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +1 -8
  84. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +2 -11
  85. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  86. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +2 -11
  87. package/src/shade/renderer/particles/shaders/chunk_particle_avboit_contribution.d.ts.map +1 -1
  88. package/src/shade/renderer/particles/shaders/chunk_particle_avboit_contribution.js +13 -0
  89. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts +3 -6
  90. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
  91. package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +49 -17
  92. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  93. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +0 -2
  94. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -1
  95. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +0 -2
  96. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  97. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +1 -5
  98. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -1
  99. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +0 -2
  100. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +1 -3
  101. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  102. package/src/shade/renderer/particles/vm/ParticleVMReference.js +4 -20
  103. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  104. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +2 -5
  105. package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.d.ts.map +1 -1
  106. package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.js +0 -2
  107. package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.d.ts.map +1 -1
  108. package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.js +0 -2
  109. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.d.ts.map +1 -1
  110. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.js +21 -0
  111. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -1
  112. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +4 -0
  113. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
  114. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +2 -1
  115. package/src/shade/renderer/scene/Mesh.js +1 -1
  116. package/src/shade/renderer/texture/ShadeTexture.js +1 -1
  117. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts +0 -19
  118. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +0 -1
  119. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +0 -30
  120. package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.d.ts +0 -11
  121. package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.d.ts.map +0 -1
  122. package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.js +0 -13
  123. /package/src/{shade/descriptor/util → core/model/object}/optional_clone.js +0 -0
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3
+ <title>SSR cross · camera motion</title><style>
4
+ body{margin:24px;background:#14161a;color:#e7e9ee;font:14px/1.5 system-ui}h1{font-size:24px;margin-bottom:8px}p{color:#acb5c5;max-width:900px}main{display:grid;grid-template-columns:minmax(0,800px) 260px;gap:24px}canvas{display:block;width:100%;height:auto;background:#08090a;touch-action:none;outline:none}canvas:focus{outline:1px solid #749ac4}label{display:block;margin:0 0 14px}input[type=range],select{width:100%}button,select{font:inherit;padding:8px;background:#29313e;color:inherit;border:1px solid #4b586d;border-radius:4px}button{margin:0 4px 8px 0}output{float:right;font-variant-numeric:tabular-nums}a{color:#9ac9ff}#status{white-space:pre-wrap}.error{color:#ff9a9a}@media(max-width:900px){main{grid-template-columns:1fr}}
5
+ </style></head><body tabindex="0">
6
+ <h1>SSR cross · camera motion</h1>
7
+ <p>The same glowing cross and colored markers as the variance lab. Fixed exposure; final-image TAA defaults off. Drag to orbit, right-drag to pan, scroll to zoom, or focus the scene and use WASD.</p>
8
+ <main><section><canvas id="view" width="800" height="500" tabindex="0" aria-label="Interactive reflection scene"></canvas><p id="status" role="status">Initializing…</p><p><a href="../ssr_variance/">Exact variance and convergence measurements</a></p></section><aside>
9
+ <label>Floor roughness <output id="roughness-value">0.30</output><input id="roughness" aria-label="Floor roughness" type="range" min="0.01" max="1" step="0.01" value="0.3"></label>
10
+ <label>Camera <select id="motion"><option value="free">Free camera</option><option value="lateral">Lateral movement</option><option value="forward">Forward movement</option><option value="orbit">Orbit movement</option><option value="still">Still camera</option></select></label>
11
+ <label>Motion speed <output id="speed-value">1.0×</output><input id="speed" aria-label="Motion speed" type="range" min="0.1" max="3" step="0.1" value="1"></label>
12
+ <label>SSR history <select id="temporal"><option value="0">Parallax</option><option value="1">Surface only</option><option value="2">No temporal history</option></select></label>
13
+ <label><input id="spatial" type="checkbox" checked> Spatial denoising</label>
14
+ <label><input id="taa" type="checkbox"> Final-image TAA</label>
15
+ <label><input id="jitter" type="checkbox"> Raster jitter</label>
16
+ <button id="motion-pause">Pause motion</button><button id="render-pause">Pause rendering</button><button id="step">Step frame</button><button id="restart">Reset camera</button><button id="reset">Reset SSR history</button>
17
+ <p>Pausing motion keeps sampling. Pausing rendering freezes the displayed frame. Ordinary camera movement preserves history; resets and material changes clear it.</p>
18
+ </aside></main><script type="module" src="./main.js"></script></body></html>
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/ssr_motion/main.js"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import Vector3 from "../../../core/geom/Vector3.js";
2
+ import { PerspectiveCamera } from "../../renderer/camera/PerspectiveCamera.js";
3
+ import { OrbitalCameraController } from "../../renderer/camera/OrbitalCameraController.js";
4
+ import { Renderer } from "../../renderer/Renderer.js";
5
+ import { ShadeIndirectLightingMode } from "../../renderer/ShadeIndirectLightingMode.js";
6
+ import { FixedProjection } from "../ssr_variance/capture.js";
7
+ import { create_scene } from "../ssr_variance/scene.js";
8
+
9
+ const element = id => document.getElementById(id);
10
+ const params = new URLSearchParams(location.search);
11
+ const number = (name, fallback, min, max) => {
12
+ const value = Number(params.get(name) ?? fallback);
13
+ return Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
14
+ };
15
+ element('roughness').value = number('roughness', 0.3, 0.01, 1);
16
+ element('speed').value = number('speed', 1, 0.1, 3);
17
+ if (['free', 'lateral', 'forward', 'orbit', 'still'].includes(params.get('motion'))) { element('motion').value = params.get('motion'); }
18
+ if (['0', '1', '2'].includes(params.get('temporal'))) { element('temporal').value = params.get('temporal'); }
19
+ element('spatial').checked = params.get('spatial') !== 'off';
20
+ for (const name of ['taa', 'jitter']) { element(name).checked = params.get(name) === 'on'; }
21
+
22
+ async function start() {
23
+ const canvas = element('view'), renderer = new Renderer();
24
+ await renderer.initialize({ context: canvas.getContext('webgpu'), pixelRatio: 1 });
25
+ renderer.feature_ssr_enabled = true;
26
+ renderer.indirect_lighting_mode = ShadeIndirectLightingMode.IBL;
27
+ for (const name of ['ssao', 'bloom', 'sharpening', 'motion_blur', 'automatic_exposure']) { renderer[`feature_${name}_enabled`] = false; }
28
+ renderer.exposure_compensation = 1;
29
+ renderer.internal_resolution_scale = 1;
30
+ renderer.feature_taa_enabled = element('taa').checked;
31
+ renderer.ssr.spatial_denoising_enabled = element('spatial').checked;
32
+ renderer.ssr.reprojection_mode = Number(element('temporal').value);
33
+ const world = await create_scene();
34
+ world.ground_material.roughness_factor = Number(element('roughness').value);
35
+ const floor_materials = new Map([[world.ground_material.roughness_factor, world.ground_material]]);
36
+ const camera = new PerspectiveCamera();
37
+ camera.near = 0.1; camera.far = 50; camera.fov = 50 * Math.PI / 180;
38
+ camera.aspect = canvas.width / canvas.height;
39
+ const controller = new OrbitalCameraController(camera, canvas);
40
+ controller.movement_speed_scale = 2;
41
+ renderer.resize(canvas.width, canvas.height);
42
+ class ProjectionControl extends FixedProjection {
43
+ record(frame) { if (!element('jitter').checked) { super.record(frame); } }
44
+ }
45
+ renderer.add_extension(new ProjectionControl());
46
+ let phase = 0, frame_count = 0, motion_paused = false;
47
+ let rendering_paused = params.has('manual'), stopped = false, request, last_time, last_status = 0;
48
+ const pose = (position, target) => {
49
+ camera.transform.setTranslation(...position);
50
+ camera.transform.lookAt(new Vector3(...target));
51
+ camera.transform.updateMatrix();
52
+ controller.look(new Vector3(...position), new Vector3(...target));
53
+ };
54
+ function status() {
55
+ element('status').textContent = `Frame ${frame_count} · roughness ${world.ground_material.roughness_factor.toFixed(2)}\n${rendering_paused ? 'Rendering paused' : motion_paused ? 'Motion paused · sampling continues' : 'Running'} · final TAA ${renderer.feature_taa_enabled ? 'on' : 'off'}`;
56
+ element('render-pause').textContent = rendering_paused ? 'Resume rendering' : 'Pause rendering';
57
+ }
58
+ function fail(error) {
59
+ stopped = true; cancelAnimationFrame(request);
60
+ element('status').textContent = error.message ?? String(error);
61
+ element('status').classList.add('error'); console.error(error);
62
+ }
63
+ function reset_camera() {
64
+ phase = 0; pose([0, 3.2, 7], [0, 0.2, -2]);
65
+ renderer.indicate_view_change(); status();
66
+ }
67
+ function set_roughness() {
68
+ const roughness = Number(element('roughness').value);
69
+ // GPU material contexts are immutable after upload. Reuse at most one
70
+ // material per slider value, and refresh the scene's material membership.
71
+ let material = floor_materials.get(roughness);
72
+ if (!material) {
73
+ material = world.ground_material.clone();
74
+ material.roughness_factor = roughness;
75
+ floor_materials.set(roughness, material);
76
+ }
77
+ if (material !== world.ground_material) {
78
+ world.scene.remove(world.ground);
79
+ world.ground.material = world.ground_material = material;
80
+ world.ground.updateMatrices();
81
+ renderer.graphics.materials.obtain(material);
82
+ world.scene.add(world.ground);
83
+ }
84
+ element('roughness-value').textContent = world.ground_material.roughness_factor.toFixed(2);
85
+ renderer.indicate_view_change(); status();
86
+ }
87
+ function frame(dt = 1 / 60, advance = true) {
88
+ if (stopped) { throw new Error('Renderer stopped after an error'); }
89
+ const mode = element('motion').value;
90
+ if (advance && !motion_paused) {
91
+ phase += dt * Number(element('speed').value) * 0.7;
92
+ const t = Math.sin(phase);
93
+ if (mode === 'free') { controller.update(dt); }
94
+ else if (mode === 'lateral') { pose([2 * t, 3.2, 7], [2 * t, 0.2, -2]); }
95
+ else if (mode === 'forward') { pose([0, 3.2, 7 + 1.6 * t], [0, 0.2, -2 + 1.6 * t]); }
96
+ else if (mode === 'orbit') { pose([4 * t, 3.2, 7 - 0.7 * t * t], [0, 0.2, -2]); }
97
+ }
98
+ if (renderer.render(camera, world.scene, dt) === false) { throw new Error('Renderer did not submit the frame'); }
99
+ frame_count++;
100
+ if (performance.now() - last_status > 250 || rendering_paused) { status(); last_status = performance.now(); }
101
+ return frame_count;
102
+ }
103
+ function tick(time) {
104
+ if (stopped) { return; }
105
+ const dt = last_time === undefined ? 1 / 60 : Math.min((time - last_time) / 1000, 0.05);
106
+ last_time = time;
107
+ try { if (!rendering_paused) { frame(dt); } }
108
+ catch (error) { fail(error); return; }
109
+ request = requestAnimationFrame(tick);
110
+ }
111
+ element('roughness').oninput = set_roughness;
112
+ element('speed').oninput = () => { element('speed-value').textContent = `${Number(element('speed').value).toFixed(1)}×`; };
113
+ element('speed').oninput();
114
+ element('motion').onchange = () => {
115
+ if (element('motion').value !== 'free') { reset_camera(); }
116
+ };
117
+ canvas.addEventListener('pointerdown', () => { canvas.focus(); element('motion').value = 'free'; motion_paused = false; element('motion-pause').textContent = 'Pause motion'; });
118
+ canvas.addEventListener('contextmenu', event => event.preventDefault());
119
+ element('temporal').onchange = () => { renderer.ssr.reprojection_mode = Number(element('temporal').value); };
120
+ element('spatial').onchange = () => { renderer.ssr.spatial_denoising_enabled = element('spatial').checked; };
121
+ element('taa').onchange = () => { renderer.feature_taa_enabled = element('taa').checked; renderer.indicate_view_change(); status(); };
122
+ element('jitter').onchange = () => renderer.indicate_view_change();
123
+ element('motion-pause').onclick = () => { motion_paused = !motion_paused; element('motion-pause').textContent = motion_paused ? 'Resume motion' : 'Pause motion'; status(); };
124
+ element('render-pause').onclick = () => { rendering_paused = !rendering_paused; status(); };
125
+ element('step').onclick = () => { rendering_paused = true; try { frame(); } catch (error) { fail(error); } };
126
+ element('restart').onclick = reset_camera;
127
+ element('reset').onclick = () => { renderer.ssr.reset_history = true; };
128
+ const device = renderer.graphics.device;
129
+ device.addEventListener('uncapturederror', event => fail(event.error));
130
+ device.lost.then(info => { if (!stopped) { fail(new Error(`GPU device lost: ${info.message}`)); } });
131
+ addEventListener('pagehide', () => { stopped = true; cancelAnimationFrame(request); controller.pointer.stop(); controller.keyboard.stop(); renderer.destroy(); device.destroy(); });
132
+ addEventListener('pageshow', event => { if (event.persisted) { location.reload(); } });
133
+ reset_camera(); set_roughness();
134
+ const lab = globalThis.ssr_motion_lab = {
135
+ renderer, camera, world, frame, pose,
136
+ step(n = 1, advance = true) {
137
+ if (!rendering_paused) { throw new Error('Pause rendering before exact stepping'); }
138
+ if (!Number.isSafeInteger(n) || n < 1) { throw new Error('Step count must be a positive integer'); }
139
+ for (let i = 0; i < n; i++) { frame(1 / 60, advance); }
140
+ return frame_count;
141
+ },
142
+ get frame_count() { return frame_count; },
143
+ };
144
+ request = requestAnimationFrame(tick);
145
+ return lab;
146
+ }
147
+ globalThis.ssr_motion_ready = start().catch(error => {
148
+ element('status').textContent = error.message ?? String(error); console.error(error); throw error;
149
+ });
@@ -5,6 +5,7 @@ body{margin:24px;background:#14161a;color:#e7e9ee;font:14px system-ui}h1{font-si
5
5
  </style></head><body>
6
6
  <h1>SSR variance lab</h1>
7
7
  <p>One ground plane · roughness 0.30 · metallic 1 · textured emissive card · fixed camera and exposure. Starts at frame zero. Rendering advances only when requested.</p>
8
+ <p><a href="../ssr_motion/">Move the camera and adjust floor roughness</a></p>
8
9
  <label>Spatial <select id="spatial"><option value="on">On</option><option value="off">Off</option></select></label>
9
10
  <label>Temporal <select id="temporal"><option value="on">On</option><option value="off">Off</option></select></label>
10
11
  <label>Raster jitter <select id="jitter"><option value="off">Off</option><option value="on">On</option></select></label>
@@ -1 +1 @@
1
- {"version":3,"file":"Geometry.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/geometry/Geometry.js"],"names":[],"mappings":"4BA2BU,MAAM;;;;AAQhB;;GAEG;AACH;IAQI;;;;OAIG;IACH,iBAEC;IAED;;;OAGG;IACH,MAFU,MAAM,CAEN;IAEV;;;OAGG;IACH,YAFU,SAAS,EAAE,CAEL;IAEhB;;;OAGG;IACH,OAFU,SAAS,GAAC,IAAI,CAEX;IAEb;;;OAGG;IACH,cAFU,YAAY,CAEa;IACnC;;;OAGG;IACH,iBAFU,YAAY,CAEgB;IAEtC;;;OAGG;IACH,OAFU,MAAM,CAEM;IAOtB;;;;;;;;;;OAUG;IACH,sBAEC;IAED;;OAEG;IACH,8BAIC;IAGD;;;;OAIG;IACH,cAHW,MAAM,GAAC,aAAa,GAClB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,aAAa,GAClB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,aAAa,SACpB,OAAO,QAQjB;IAED;;;;OAIG;IACH,cAHW,MAAM,GAAC,aAAa,GAClB,OAAO,CAInB;IAED;;;;OAIG;IACH,cAHW,QAAQ,GACN,OAAO,CAYnB;IAED,eAQC;IAED;;;;OAIG;IACH,mBAHW,MAAM,GACL,SAAS,GAAC,SAAS,CAkB9B;IAED;;;;OAIG;IACH,mBAHW,MAAM,GACJ,OAAO,CAMnB;IAED;;;OAGG;IACH,wBAFW,SAAS,QAQnB;IAED;;;OAGG;IACH,wBAFW,SAAS,QASnB;IAED;;;;OAIG;IACH,sBAHW,MAAM,GACL,SAAS,GAAC,SAAS,CAiB9B;IAED;;;OAGG;IACH,kBAFY,MAAM,CAWjB;IAED;;;OAGG;IACH,iBAFY,MAAM,CAYjB;IAED;;;;OAIG;IACH,qBAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAIC;IAED;;OAEG;IACH,mBASC;IAED,qBAIC;IAED;;OAEG;IACH,qCAoBC;IAED,8BAaC;IAED,8BAKC;IAED,2BAeC;IAED,sBAOC;IAED,uBA4BC;IAoFD,uBAOC;IAED,wBAWC;IAED;;;OAGG;IACH,YAFW,QAAQ,QAmBlB;IAED;;;OAGG;IACH,SAFY,QAAQ,CAQnB;IAED,eAEC;IAED;;;OAGG;IACH,iCAYC;IAGL;;;OAGG;IACH,qBAFU,OAAO,CAEY;;CAN5B;0BAvkByB,gBAAgB"}
1
+ {"version":3,"file":"Geometry.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/geometry/Geometry.js"],"names":[],"mappings":"4BA4BU,MAAM;;;;AAQhB;;GAEG;AACH;IAQI;;;;OAIG;IACH,iBAEC;IAED;;;OAGG;IACH,MAFU,MAAM,CAEN;IAEV;;;OAGG;IACH,YAFU,SAAS,EAAE,CAEL;IAEhB;;;OAGG;IACH,OAFU,SAAS,GAAC,IAAI,CAEX;IAEb;;;OAGG;IACH,cAFU,YAAY,CAEa;IACnC;;;OAGG;IACH,iBAFU,YAAY,CAEgB;IAEtC;;;OAGG;IACH,OAFU,MAAM,CAEM;IAOtB;;;;;;;;;;OAUG;IACH,sBAEC;IAED;;OAEG;IACH,8BAIC;IAGD;;;;OAIG;IACH,cAHW,MAAM,GAAC,aAAa,GAClB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,aAAa,GAClB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,aAAa,SACpB,OAAO,QAQjB;IAED;;;;OAIG;IACH,cAHW,MAAM,GAAC,aAAa,GAClB,OAAO,CAInB;IAED;;;;OAIG;IACH,cAHW,QAAQ,GACN,OAAO,CAYnB;IAED,eAQC;IAED;;;;OAIG;IACH,mBAHW,MAAM,GACL,SAAS,GAAC,SAAS,CAkB9B;IAED;;;;OAIG;IACH,mBAHW,MAAM,GACJ,OAAO,CAMnB;IAED;;;OAGG;IACH,wBAFW,SAAS,QAQnB;IAED;;;OAGG;IACH,wBAFW,SAAS,QASnB;IAED;;;;OAIG;IACH,sBAHW,MAAM,GACL,SAAS,GAAC,SAAS,CAiB9B;IAED;;;OAGG;IACH,kBAFY,MAAM,CAWjB;IAED;;;OAGG;IACH,iBAFY,MAAM,CAYjB;IAED;;;;OAIG;IACH,qBAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAIC;IAED;;OAEG;IACH,mBASC;IAED,qBAIC;IAED;;OAEG;IACH,qCAoBC;IAED,8BAaC;IAED,8BAKC;IAED,2BAeC;IAED,sBAOC;IAED,uBA4BC;IAoFD,uBAOC;IAED,wBAWC;IAED;;;OAGG;IACH,YAFW,QAAQ,QAelB;IAED;;;OAGG;IACH,SAFY,QAAQ,CAQnB;IAED,eAEC;IAED;;;OAGG;IACH,iCAYC;IAGL;;;OAGG;IACH,qBAFU,OAAO,CAEY;;CAN5B;0BAnkByB,gBAAgB"}
@@ -14,8 +14,9 @@ import {
14
14
  import {
15
15
  geometry_compute_vertex_normals_unindexed
16
16
  } from "../../../engine/graphics/geometry/buffered/geometry_compute_vertex_normals_unindexed.js";
17
- import { optional_equality } from "../../descriptor/util/optional_equality.js";
18
- import { optional_hash } from "../../descriptor/util/optional_hash.js";
17
+ import { optional_clone } from "../../../core/model/object/optional_clone.js";
18
+ import { optional_equality } from "../../../core/model/object/optional_equality.js";
19
+ import { optional_hash } from "../../../core/model/object/optional_hash.js";
19
20
  import { Attribute } from "./Attribute.js";
20
21
  import { build_tangent_from_normal } from "./normals/build_tangent_from_normal.js";
21
22
  import { geometry_build_tangents } from "./normals/geometry_build_tangents.js";
@@ -551,11 +552,7 @@ export class Geometry {
551
552
 
552
553
  this.name = other.name;
553
554
 
554
- if (other.index !== null) {
555
- this.index = other.index.clone();
556
- } else {
557
- this.index = null;
558
- }
555
+ this.index = optional_clone(other.index);
559
556
 
560
557
  this.attributes = other.attributes.map(a => a.clone());
561
558
 
@@ -1,8 +1,8 @@
1
1
  import { combine_hash } from "../../../core/collection/array/combine_hash.js";
2
2
  import { Color } from "../../../core/color/Color.js";
3
3
  import LinearModifier from "../../../core/model/stat/LinearModifier.js";
4
- import { optional_equality } from "../../descriptor/util/optional_equality.js";
5
- import { optional_hash } from "../../descriptor/util/optional_hash.js";
4
+ import { optional_equality } from "../../../core/model/object/optional_equality.js";
5
+ import { optional_hash } from "../../../core/model/object/optional_hash.js";
6
6
  import { ShadeMaterial } from "./ShadeMaterial.js";
7
7
 
8
8
  export class StandardShadeMaterial extends ShadeMaterial {
@@ -22,7 +22,7 @@ the decision to spawn is the GPU's.
22
22
  - Distinct per-particle lifecycle: **no** built-in age/position — the schema + program define state.
23
23
  - Billboard renderer reuses engine shader chunks incl. lighting (forward-shaded, optional per emitter).
24
24
  - Blend modes (alpha + additive) via a **single** premultiplied blend setup.
25
- - `AnimationCurve` sampling during simulation (reuse `chunk_animation_curve_evaluate`).
25
+ - Authored curves and RGB/RGBA gradients compiled into arithmetic for simulation.
26
26
  - Spawning decided on the GPU, per emitter, with the CPU able to ask for bursts through the same path.
27
27
  - No per-frame allocation on the CPU beyond the frame graph's own records.
28
28
 
@@ -159,7 +159,7 @@ recursion, no data-dependent jumps — conditionals are predicated via CMP/SELEC
159
159
  Word 0 packs the opcode (bits 0..7), the lane count (8..10), each source's `VM_KIND` (two bits per
160
160
  slot at 16..21) and the destination slot (24..31). A source word means what its kind says: `REG`
161
161
  is a base slot plus a swizzle, `IMM` is an `f32` bit pattern read in every lane, and `NONE` is a
162
- raw field the opcode reads for itself (attribute word-offset+comp / curve handle / builtin id /
162
+ raw field the opcode reads for itself (attribute word-offset+comp / builtin id /
163
163
  pool index).
164
164
  - Constant pool: `array<vec4f>` per program, referenced by `LOAD_CONST`.
165
165
  - Two entry sections per program: **INIT** and **UPDATE** (offsets in the emitter record). INIT
@@ -167,8 +167,6 @@ recursion, no data-dependent jumps — conditionals are predicated via CMP/SELEC
167
167
  - Builtins (`LOAD_BUILTIN`): DT, EMITTER_AGE, EMITTER_POSITION, EMITTER_DIRECTION, EMITTER_UP,
168
168
  PARTICLE_INDEX, SPAWN_FRACTION, TIME … assembled per-invocation by the host shader. The emitter's
169
169
  position and axes are its node's `global` out of the scene database.
170
- - `CURVE dst, handle, s0`: `dst.x = animation_curve_evaluate(curves[handle], s0.x)` — reuses the
171
- engine's GPU AnimationCurve chunk + database.
172
170
 
173
171
  #### Two interpreters
174
172
 
@@ -212,11 +210,13 @@ A core `NodeGraph` holds particle nodes (ports + parameters) for INIT and UPDATE
212
210
  2. lowers each node to VM ops (nodes emit via a small builder that allocates result registers),
213
211
  3. linear-scan register allocation with liveness,
214
212
  4. interns constants into the pool,
215
- 5. resolves curve/attribute references,
216
- 6. emits `Program { init, update, constants, reg_count, attributes, curves }`.
213
+ 5. resolves attribute references,
214
+ 6. emits `Program { init, update, constants, reg_count, attributes }`.
217
215
 
218
216
  Standard node library: constants, attribute get/set, arithmetic/vector/math, random (uniform/sphere/
219
- disk/cone), curve-sample, noise (curl), integrate, gravity, emit-shapes, compare/select, kill-if.
217
+ disk/cone), authored curves and RGB/RGBA gradients, noise (curl), integrate, gravity, emit-shapes,
218
+ compare/select, kill-if. Curves and gradients expand into graph nodes that emit ordinary VM
219
+ arithmetic; no animation-table binding or sampling opcode is needed.
220
220
 
221
221
  ### Emitters are scene nodes
222
222
 
@@ -684,7 +684,6 @@ src/shade/renderer/particles/
684
684
  warmup/chunk_particle_warmup_step.js one particle's advance: per-lane decode, jittered tick
685
685
  warmup/shader_particle_warmup_advance.js the advance over the fast file (+ _wide.js)
686
686
  warmup/graph_particle_warmup.js the loop, recorded on a frame that registered a pre-warm
687
- shaders/chunk_particle_curve_disabled.js the no-curves-bound CURVE provider
688
687
  shaders/chunk_particle_billboard_vertex.js the billboard vertex stage, shared by every draw
689
688
  shaders/shader_particle_avboit_*.js occupancy / splat / draw through AVBOIT
690
689
  bounds/* the measured emitter box: accumulate, publish
@@ -743,6 +742,5 @@ was seeded.
743
742
  Done: `Renderer.feature_particles_enabled` (off by default), `Renderer.particles(scene_ctx)` creating
744
743
  one system per scene context on first use, `simulate` recorded before the transparency pass and the
745
744
  system passed to the AVBOIT orchestrator as its side channel. Remaining:
746
- 1. For lit emitters, compile a simulate/render variant that swaps the disabled curve/lighting hooks
747
- for `chunk_particle_curve_animation` (+ bind `GPUAnimationManager.database.buffer`) and a
748
- shading-chunk-backed lighting hook (+ the `SHADING_LIGHT_RESOURCE_GROUP`).
745
+ 1. For lit emitters, compile a render variant that swaps the disabled lighting hook for a
746
+ shading-chunk-backed lighting hook (+ the `SHADING_LIGHT_RESOURCE_GROUP`).
@@ -1 +1 @@
1
- {"version":3,"file":"GPUParticleSystem.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/GPUParticleSystem.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GPUParticleSystem.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/GPUParticleSystem.js"],"names":[],"mappings":"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"}
@@ -41,6 +41,8 @@ const WORD = Uint32Array.BYTES_PER_ELEMENT;
41
41
  const STORAGE = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
42
42
  const INDIRECT = GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC;
43
43
 
44
+ const DEFAULT_CAPACITY = 524288;
45
+
44
46
  /**
45
47
  * The GPU particle system: a feature of a scene, in the mould of `ReSTIRDI` — construct once
46
48
  * against a {@link GPUSceneContext}, and contribute the frame's passes through
@@ -299,7 +301,7 @@ export class GPUParticleSystem {
299
301
  * @param {number} [options.capacity] max simultaneous particles
300
302
  * @param {number} [options.spawn_command_capacity] most {@link spawn} calls one frame keeps
301
303
  */
302
- constructor(graphics, scene_context, { capacity = 1 << 16, spawn_command_capacity = 256 } = {}) {
304
+ constructor(graphics, scene_context, { capacity = DEFAULT_CAPACITY, spawn_command_capacity = 256 } = {}) {
303
305
  assert.defined(graphics, "graphics");
304
306
  assert.defined(scene_context, "scene_context");
305
307
  assert.equal(scene_context.isGPUSceneContext, true, "scene_context.isGPUSceneContext !== true");
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Every stock operation that takes no parameters of its own is a method here, taken from the library
11
11
  * rather than listed — a type added to it is callable the same day. The rest — `const`, `swizzle`,
12
- * `random`, `curve` and the attribute nodes — need a parameter and are reached through
12
+ * `random`, `curveLiteral` and the attribute nodes — need a parameter and are reached through
13
13
  * {@link #op}, or through the shorthands below where there is an obvious one.
14
14
  *
15
15
  * A number where a value is expected becomes a literal, so `g.mul(v, 0.5)` is what it looks like.
@@ -17,7 +17,7 @@ import {
17
17
  *
18
18
  * Every stock operation that takes no parameters of its own is a method here, taken from the library
19
19
  * rather than listed — a type added to it is callable the same day. The rest — `const`, `swizzle`,
20
- * `random`, `curve` and the attribute nodes — need a parameter and are reached through
20
+ * `random`, `curveLiteral` and the attribute nodes — need a parameter and are reached through
21
21
  * {@link #op}, or through the shorthands below where there is an obvious one.
22
22
  *
23
23
  * A number where a value is expected becomes a literal, so `g.mul(v, 0.5)` is what it looks like.
@@ -1 +1 @@
1
- {"version":3,"file":"ParticleNodeRegistry.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/ParticleNodeRegistry.js"],"names":[],"mappings":"AAqrBA;;;;;;;GAOG;AACH,8CAFa,uBAAuB,EAAE,CAIrC;AAED;;;;;GAKG;AACH,oDAJW,MAAM,GACJ,uBAAuB,CAWnC;AAhrBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;;;GASG;AACH,kCAFU,QAAQ,CAE2C;AAE7D;;;GAGG;AACH,wCAFU,MAAM,CAE+B;AAE/C;;GAEG;AACH,qCAFU,YAAY,CAEmC;wCAhEjB,8BAA8B;yBAZ7C,oDAAoD;6BAFhD,wDAAwD"}
1
+ {"version":3,"file":"ParticleNodeRegistry.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/ParticleNodeRegistry.js"],"names":[],"mappings":"AA+qBA;;;;;;;GAOG;AACH,8CAFa,uBAAuB,EAAE,CAIrC;AAED;;;;;GAKG;AACH,oDAJW,MAAM,GACJ,uBAAuB,CAWnC;AA1qBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;;;GASG;AACH,kCAFU,QAAQ,CAE2C;AAE7D;;;GAGG;AACH,wCAFU,MAAM,CAE+B;AAE/C;;GAEG;AACH,qCAFU,YAAY,CAEmC;wCAhEjB,8BAA8B;yBAZ7C,oDAAoD;6BAFhD,wDAAwD"}
@@ -90,12 +90,14 @@ for (const type of PARTICLE_ALL_VALUE_TYPES) {
90
90
  *
91
91
  * IDs are handed out in declaration order rather than by {@link NodeDescription}'s global counter,
92
92
  * so they do not drift with unrelated node usage elsewhere in the engine. Appending new node types
93
- * at the end of the library is therefore safe; reordering or removing existing ones renumbers every
94
- * node that follows, which would invalidate already-serialized graphs.
93
+ * at the end of the library is therefore safe. Removed types leave their IDs unused so existing
94
+ * serialized graphs retain the same IDs for every remaining type.
95
95
  *
96
96
  * @type {Map<string, ParticleNodeDescription>}
97
97
  */
98
- const descriptions_by_name = new Map();
98
+ const descriptions_by_name = new Map();
99
+
100
+ let next_description_id = 0;
99
101
 
100
102
  /**
101
103
  * @param {string} name
@@ -123,7 +125,7 @@ function define(name, inputs, emit, {
123
125
  const description = new ParticleNodeDescription();
124
126
 
125
127
  description.name = name;
126
- description.id = descriptions_by_name.size;
128
+ description.id = next_description_id++;
127
129
 
128
130
  // what an author reads is decided in one table, away from the lowering; see
129
131
  // ./particle_node_presentation.js
@@ -572,14 +574,8 @@ define("compare", ["a", "b"], (s, c) => {
572
574
  }, MASK_RESULT);
573
575
  define("select", ["a", "b", "condition"], (s, c) => s.emit(VM_OP.SELECT, c.out, c.in("a"), c.in("b"), c.in("condition"), c.width), blend_controlled_by("condition"));
574
576
 
575
- // --- curves ---
576
- // One lane of `t` in, one value out — which the opcode writes across every lane it is asked for, so
577
- // a curve read wider than a scalar is the broadcast for free.
578
- define("curve", ["t"], (s, c) => s.curve(c.out, c.parameters.handle, c.in("t"), c.width), {
579
- infer_types: () => ({ output: PARTICLE_TYPE_F32, inputs: { t: PARTICLE_TYPE_F32 } }),
580
- operation_width: ({ demand }) => demand,
581
- input_widths: () => ({ t: 1 }),
582
- });
577
+ // ID 39 belonged to the removed animation-table sampler. Keep it unused.
578
+ next_description_id++;
583
579
 
584
580
  // --- semantic sources ---
585
581
  // Appended rather than filed with the other sources, because a node type's id is its position in
@@ -665,9 +661,7 @@ function expanding(name, inputs, output, body) {
665
661
  return description;
666
662
  }
667
663
 
668
- // One curve, written into the program. Not the `curve` node above: that one samples a row of the
669
- // host's animation-curve table and this one *is* the curve, the way `color` is a colour rather than
670
- // the name of one.
664
+ // One authored curve, written into the program as arithmetic.
671
665
  expanding(
672
666
  "curveLiteral",
673
667
  [{ name: "t", type: PARTICLE_TYPE_F32 }],
@@ -1 +1 @@
1
- {"version":3,"file":"particle_group_library.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_group_library.js"],"names":[],"mappings":"AAoBA;;;;;;;;;;GAUG;AACH,+CAHW,OAAO,wBAAwB,EAAE,iBAAiB,GAChD,OAAO,wBAAwB,EAAE,iBAAiB,CAgB9D;AAED;;;;GAIG;AACH,mCAFa,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,CAIhE;AAED;;;;;GAKG;AACH,yCAHW,MAAM,GACJ,OAAO,wBAAwB,EAAE,iBAAiB,GAAC,SAAS,CAIxE;AAED;;;;;GAKG;AACH,6CAHW,MAAM,GACJ,OAAO,CAInB"}
1
+ {"version":3,"file":"particle_group_library.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_group_library.js"],"names":[],"mappings":"AAoBA;;;;;;;;;;GAUG;AACH,+CAHW,OAAO,wBAAwB,EAAE,iBAAiB,GAChD,OAAO,wBAAwB,EAAE,iBAAiB,CAqB9D;AAED;;;;GAIG;AACH,mCAFa,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,CAIhE;AAED;;;;;GAKG;AACH,yCAHW,MAAM,GACJ,OAAO,wBAAwB,EAAE,iBAAiB,GAAC,SAAS,CAIxE;AAED;;;;;GAKG;AACH,6CAHW,MAAM,GACJ,OAAO,CAInB"}
@@ -38,9 +38,14 @@ export function register_particle_group(group) {
38
38
 
39
39
  // The group node itself, and the two boundary blocks its body is wired to: a view that opens the
40
40
  // group draws those, and everything drawn needs a box generated for it.
41
- PARTICLE_NODE_REGISTRY.addNode(group.description);
42
- PARTICLE_NODE_REGISTRY.addNode(group.inputNode.description);
43
- PARTICLE_NODE_REGISTRY.addNode(group.outputNode.description);
41
+ // Stock types leave holes when removed. Allocate after their highest ID rather than relying
42
+ // on NodeDescription's global counter, which counts constructions, not reserved IDs.
43
+ let id = Math.max(...PARTICLE_NODE_REGISTRY.nodes.keys()) + 1;
44
+
45
+ for (const description of [group.description, group.inputNode.description, group.outputNode.description]) {
46
+ description.id = id++;
47
+ PARTICLE_NODE_REGISTRY.addNode(description);
48
+ }
44
49
 
45
50
  return group;
46
51
  }
@@ -91,8 +91,6 @@ export const PARTICLE_PARAMETER_KIND: Readonly<{
91
91
  ENUM: "enum";
92
92
  /** one to four lane selectors in `[0, 4)`, one per output lane */
93
93
  SWIZZLE: "swizzle";
94
- /** a non-negative integer — a curve handle */
95
- INTEGER: "integer";
96
94
  /**
97
95
  * an {@link ../../../../engine/animation/curve/AnimationCurve.js AnimationCurve}, as the
98
96
  * `{keys: [...]}` record that class writes and reads
@@ -1 +1 @@
1
- {"version":3,"file":"particle_node_parameters.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_node_parameters.js"],"names":[],"mappings":"AAwMA;;;;;;;;;;;;;;;;;GAiBG;AACH,sDAHW,MAAM,GACJ,MAAM,CAIlB;AAwBD;;;;;;GAMG;AACH,+CAJW,MAAM,GACJ,qBAAqB,EAAE,CAcnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,uDANW,MAAM;IAEyD,MAAM,GAArE,OAAO,6BAA6B,EAAE,cAAc;IAElD,MAAM,CAmBlB;AAED;;;;GAIG;AACH,2CAFa,MAAM;IAAC,WAAW,EAAE,OAAO,8BAA8B,EAAE,uBAAuB,CAAC;IAAC,UAAU,EAAE,qBAAqB,EAAE,CAAA;CAAC,CAAC,CAOrI;;;;sCA7RS,MAAM;AApBhB;;;;;;;;;;;;;;;;GAgBG;AAEH;;;GAGG;AACH;IACI,0FAA0F;;IAE1F,kFAAkF;;IAElF,mFAAmF;;IAEnF,2EAA2E;;IAE3E,kEAAkE;;IAElE,8CAA8C;;IAE9C;;;OAGG;;IAEH;;;OAGG;;GAEJ;AAEH;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AACH,gDAFU,MAAM,EAAE,CAWf;AAEH;;;;;GAKG;AACH;QAFiB,MAAM,GAAE,MAAM;EAI7B;AAEF;;;;;GAKG;AACH;QAFiB,MAAM,GAAE,qBAAqB,EAAE;EA8F7C;;;;;;;;UA7IW,MAAM;;;;UACN,MAAM;;;;;;;;;;YAGC,MAAM;;;;;;;;YACb,OAAO;;;;kBAIP,MAAM"}
1
+ {"version":3,"file":"particle_node_parameters.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_node_parameters.js"],"names":[],"mappings":"AA+LA;;;;;;;;;;;;;;;;;GAiBG;AACH,sDAHW,MAAM,GACJ,MAAM,CAIlB;AAwBD;;;;;;GAMG;AACH,+CAJW,MAAM,GACJ,qBAAqB,EAAE,CAcnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,uDANW,MAAM;IAEyD,MAAM,GAArE,OAAO,6BAA6B,EAAE,cAAc;IAElD,MAAM,CAmBlB;AAED;;;;GAIG;AACH,2CAFa,MAAM;IAAC,WAAW,EAAE,OAAO,8BAA8B,EAAE,uBAAuB,CAAC;IAAC,UAAU,EAAE,qBAAqB,EAAE,CAAA;CAAC,CAAC,CAOrI;;;;sCApRS,MAAM;AApBhB;;;;;;;;;;;;;;;;GAgBG;AAEH;;;GAGG;AACH;IACI,0FAA0F;;IAE1F,kFAAkF;;IAElF,mFAAmF;;IAEnF,2EAA2E;;IAE3E,kEAAkE;;IAElE;;;OAGG;;IAEH;;;OAGG;;GAEJ;AAEH;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AACH,gDAFU,MAAM,EAAE,CAWf;AAEH;;;;;GAKG;AACH;QAFiB,MAAM,GAAE,MAAM;EAI7B;AAEF;;;;;GAKG;AACH;QAFiB,MAAM,GAAE,qBAAqB,EAAE;EAuF7C;;;;;;;;UAtIW,MAAM;;;;UACN,MAAM;;;;;;;;;;YAGC,MAAM;;;;;;;;YACb,OAAO;;;;kBAIP,MAAM"}
@@ -37,8 +37,6 @@ export const PARTICLE_PARAMETER_KIND = Object.freeze({
37
37
  ENUM: "enum",
38
38
  /** one to four lane selectors in `[0, 4)`, one per output lane */
39
39
  SWIZZLE: "swizzle",
40
- /** a non-negative integer — a curve handle */
41
- INTEGER: "integer",
42
40
  /**
43
41
  * an {@link ../../../../engine/animation/curve/AnimationCurve.js AnimationCurve}, as the
44
42
  * `{keys: [...]}` record that class writes and reads
@@ -172,13 +170,6 @@ export const PARTICLE_NODE_PARAMETER_SCHEMA = Object.freeze({
172
170
  description: "Which input lane each output lane reads; one to four of them, and that count is the result's width.",
173
171
  }],
174
172
 
175
- curve: [{
176
- name: "handle",
177
- kind: PARTICLE_PARAMETER_KIND.INTEGER,
178
- default: 0,
179
- description: "Row of the animation-curve table to sample.",
180
- }],
181
-
182
173
  curveLiteral: [{
183
174
  name: "curve",
184
175
  kind: PARTICLE_PARAMETER_KIND.CURVE,
@@ -1 +1 @@
1
- {"version":3,"file":"particle_node_presentation.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_node_presentation.js"],"names":[],"mappings":"AA2UA;;;;;;;GAOG;AACH,iDAHW,MAAM,GACJ;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAIvD;AA3SD;;;;;;;;GAQG;AACH;QAFiB,MAAM,GAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;EAwRjE"}
1
+ {"version":3,"file":"particle_node_presentation.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_node_presentation.js"],"names":[],"mappings":"AAiUA;;;;;;;GAOG;AACH,iDAHW,MAAM,GACJ;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAIvD;AAjSD;;;;;;;;GAQG;AACH;QAFiB,MAAM,GAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;EA8QjE"}
@@ -301,17 +301,7 @@ export const PARTICLE_NODE_PRESENTATION = Object.freeze({
301
301
  },
302
302
 
303
303
  // --- ramps ---
304
- // Two ways to have a curve, and the labels say which is which. The table one came first and was
305
- // called just "Curve"; it is renamed here rather than leaving the new one to explain itself,
306
- // because "the one that samples the host's table" is the surprising half of the pair and the
307
- // one an author has to have decided to want.
308
- curve: {
309
- label: "Curve Table",
310
- icon: `M5 18C10 18 9 6 19 6 ${dot(5, 18, 1.6)} ${dot(19, 6, 1.6)}`,
311
- help: "sample a row of the host's animation-curve table at t",
312
- },
313
304
  curveLiteral: {
314
- // the same curve with its keys on it: this one is the curve rather than a handle to one
315
305
  label: "Curve",
316
306
  icon: `M4 19C10 19 8 5 20 5 ${dot(4, 19, 1.8)} ${dot(12, 12, 1.8)} ${dot(20, 5, 1.8)}`,
317
307
  help: "a curve of your own, written into the program: t in, its value out",
@@ -128,14 +128,8 @@ export function roll_out_particle_gradient(g: import("./ParticleGroupBuilder.js"
128
128
  * {@link ./ParticleNodeDescription.js ParticleNodeDescription#expand}), and this is what they expand
129
129
  * *into*: stock nodes, which is to say VM instructions, with the curve rolled out along them.
130
130
  *
131
- * ## Rolled out, not sampled
132
- *
133
- * There is a `CURVE` opcode, and it is not this. That one samples a row of the animation-curve table
134
- * a host uploads, which is the right answer for a curve an author is dragging around at runtime and
135
- * the wrong one for a curve that is part of the effect: it costs a table, a row, a handle to keep in
136
- * step with it, and a texture fetch. A ramp with four stops rolls out to about twenty instructions
137
- * of pure arithmetic, needs nothing from the host, and constant-folds where an author has left a
138
- * segment flat.
131
+ * A ramp with four stops rolls out to about twenty instructions of pure arithmetic, needs nothing
132
+ * from the host, and constant-folds where an author has left a segment flat.
139
133
  *
140
134
  * ## How a piecewise function becomes straight-line code
141
135
  *
@@ -1 +1 @@
1
- {"version":3,"file":"particle_ramp.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_ramp.js"],"names":[],"mappings":"AA2FA;;;;;;;;GAQG;AACH,0CAFa,sBAAsB,CASlC;AAED;;;;GAIG;AACH,6CAFa,oBAAoB,EAAE,CAOlC;AAUD;;;;;;;;;GASG;AACH,+CAFa,OAAO,CAanB;AAED;;;;;GAKG;AACH,kDAFa,OAAO,CAanB;AAED;;;;;;;;;GASG;AACH,2CAHW,sBAAsB,GACpB,gBAAgB,EAAE,CAI9B;AAED;;;;;;GAMG;AACH,gDAHW,sBAAsB,GACpB,cAAc,CAQ1B;AAED;;;;;GAKG;AACH,qDAHW,cAAc,GACZ,sBAAsB,CAWlC;AAED;;;;;GAKG;AACH,+CAHW,oBAAoB,EAAE,GACpB,oBAAoB,EAAE,CAMlC;AAED;;;;;;;GAOG;AACH,gDAJW,oBAAoB,EAAE,KACtB,MAAM,GACJ,MAAM,EAAE,CAsBpB;AA8BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,2CALW,OAAO,2BAA2B,EAAE,oBAAoB,iBAExD,sBAAsB,OA+ChC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,8CANW,OAAO,2BAA2B,EAAE,oBAAoB,iBAExD,oBAAoB,EAAE,cACtB,MAAM,OA8BhB;AA/YD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH;;;;;;;;;GASG;AAEH;;;;GAIG;AAEH;;;;;;GAMG;AAEH;;;GAGG;AACH,iDAFU,MAAM,CAEoC;;;;;;UAxBtC,MAAM;WACN,MAAM;eACN,MAAM;gBACN,MAAM;;;;;qCAMP;IAAC,IAAI,EAAE,gBAAgB,EAAE,CAAA;CAAC;;;;;;mCAQ1B;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAC;+BAlFjB,sDAAsD"}
1
+ {"version":3,"file":"particle_ramp.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_ramp.js"],"names":[],"mappings":"AAqFA;;;;;;;;GAQG;AACH,0CAFa,sBAAsB,CASlC;AAED;;;;GAIG;AACH,6CAFa,oBAAoB,EAAE,CAOlC;AAUD;;;;;;;;;GASG;AACH,+CAFa,OAAO,CAanB;AAED;;;;;GAKG;AACH,kDAFa,OAAO,CAanB;AAED;;;;;;;;;GASG;AACH,2CAHW,sBAAsB,GACpB,gBAAgB,EAAE,CAI9B;AAED;;;;;;GAMG;AACH,gDAHW,sBAAsB,GACpB,cAAc,CAQ1B;AAED;;;;;GAKG;AACH,qDAHW,cAAc,GACZ,sBAAsB,CAWlC;AAED;;;;;GAKG;AACH,+CAHW,oBAAoB,EAAE,GACpB,oBAAoB,EAAE,CAMlC;AAED;;;;;;;GAOG;AACH,gDAJW,oBAAoB,EAAE,KACtB,MAAM,GACJ,MAAM,EAAE,CAsBpB;AA8BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,2CALW,OAAO,2BAA2B,EAAE,oBAAoB,iBAExD,sBAAsB,OA+ChC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,8CANW,OAAO,2BAA2B,EAAE,oBAAoB,iBAExD,oBAAoB,EAAE,cACtB,MAAM,OA8BhB;AAzYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH;;;;;;;;;GASG;AAEH;;;;GAIG;AAEH;;;;;;GAMG;AAEH;;;GAGG;AACH,iDAFU,MAAM,CAEoC;;;;;;UAxBtC,MAAM;WACN,MAAM;eACN,MAAM;gBACN,MAAM;;;;;qCAMP;IAAC,IAAI,EAAE,gBAAgB,EAAE,CAAA;CAAC;;;;;;mCAQ1B;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAC;+BA5EjB,sDAAsD"}
@@ -11,14 +11,8 @@ import { AnimationCurve } from "../../../../engine/animation/curve/AnimationCurv
11
11
  * {@link ./ParticleNodeDescription.js ParticleNodeDescription#expand}), and this is what they expand
12
12
  * *into*: stock nodes, which is to say VM instructions, with the curve rolled out along them.
13
13
  *
14
- * ## Rolled out, not sampled
15
- *
16
- * There is a `CURVE` opcode, and it is not this. That one samples a row of the animation-curve table
17
- * a host uploads, which is the right answer for a curve an author is dragging around at runtime and
18
- * the wrong one for a curve that is part of the effect: it costs a table, a row, a handle to keep in
19
- * step with it, and a texture fetch. A ramp with four stops rolls out to about twenty instructions
20
- * of pure arithmetic, needs nothing from the host, and constant-folds where an author has left a
21
- * segment flat.
14
+ * A ramp with four stops rolls out to about twenty instructions of pure arithmetic, needs nothing
15
+ * from the host, and constant-folds where an author has left a segment flat.
22
16
  *
23
17
  * ## How a piecewise function becomes straight-line code
24
18
  *
@@ -231,12 +231,6 @@ function check_parameters(node, description, layout, error) {
231
231
  }
232
232
  break;
233
233
 
234
- case PARTICLE_PARAMETER_KIND.INTEGER:
235
- if (!Number.isInteger(value) || value < 0) {
236
- error(node.id, `Parameter '${parameter.name}' must be a non-negative whole number.`);
237
- }
238
- break;
239
-
240
234
  // A curve and a gradient are the only parameters with a shape underneath them, and both
241
235
  // are written out into instructions rather than read at runtime — so a malformed one is
242
236
  // a compile that throws rather than a value that misbehaves, and it is worth catching
@@ -1 +1 @@
1
- {"version":3,"file":"graph_particles_avboit.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/graph_particles_avboit.js"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;;;;GAeG;AACH;IARkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IAC/D,MAAM,EAArB,MAAM;IACoB,kBAAkB,EAA5C,UAAU,MAAM,CAAC;IACF,SAAS,EAAxB,MAAM;IACS,gBAAgB,EAA/B,MAAM;IACS,mBAAmB,EAAlC,MAAM;IACJ,MAAM,CA8ClB;AAED;;;;;;;;;;;;;;;GAeG;AACH;IAZkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IAC/D,MAAM,EAArB,MAAM;IACS,IAAI,EAAnB,MAAM;IACwD,MAAM,EAApE;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC;IAC9B,KAAK;IACT,aAAa;IACjB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,gBAAgB,EAA/B,MAAM;IACS,SAAS,EAAxB,MAAM;IACJ;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CA8EjE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;IAjBkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IACnB,QAAQ,EAAnE,OAAO,2BAA2B,EAAE,cAAc;IACnC,MAAM,EAArB,MAAM;IACS,aAAa,EAA5B,MAAM;IACS,IAAI,EAAnB,MAAM;IACS,QAAQ,EAAvB,MAAM;IACS,eAAe,EAA9B,MAAM;IACS,oBAAoB,EAAnC,MAAM;IACsE,YAAY,EAAxF;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC;IAC5C,KAAK;IACT,aAAa;IACjB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,gBAAgB,EAA/B,MAAM;IACS,SAAS,EAAxB,MAAM;IACJ;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAoG/E"}
1
+ {"version":3,"file":"graph_particles_avboit.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/particles/graph_particles_avboit.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;GAeG;AACH;IARkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IAC/D,MAAM,EAArB,MAAM;IACoB,kBAAkB,EAA5C,UAAU,MAAM,CAAC;IACF,SAAS,EAAxB,MAAM;IACS,gBAAgB,EAA/B,MAAM;IACS,mBAAmB,EAAlC,MAAM;IACJ,MAAM,CA8ClB;AAED;;;;;;;;;;;;;;;GAeG;AACH;IAZkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IAC/D,MAAM,EAArB,MAAM;IACS,IAAI,EAAnB,MAAM;IACwD,MAAM,EAApE;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC;IAC9B,KAAK;IACT,aAAa;IACjB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,gBAAgB,EAA/B,MAAM;IACS,SAAS,EAAxB,MAAM;IACJ;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CA8EjE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;IAjBkG,KAAK,EAA5F,OAAO,2DAA2D,EAAE,UAAU;IACnB,QAAQ,EAAnE,OAAO,2BAA2B,EAAE,cAAc;IACnC,MAAM,EAArB,MAAM;IACS,aAAa,EAA5B,MAAM;IACS,IAAI,EAAnB,MAAM;IACS,QAAQ,EAAvB,MAAM;IACS,eAAe,EAA9B,MAAM;IACS,oBAAoB,EAAnC,MAAM;IACsE,YAAY,EAAxF;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC;IAC5C,KAAK;IACT,aAAa;IACjB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,gBAAgB,EAA/B,MAAM;IACS,SAAS,EAAxB,MAAM;IACJ;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CA2G/E"}
@@ -6,7 +6,9 @@ import { AVBOIT_OCCUPANCY_SETTINGS_STRUCT } from "../rasterize/native/avboit/sha
6
6
  import { STATIC_GRAPHICS_ENGINE_ASSETS } from "../STATIC_GRAPHICS_ENGINE_ASSETS.js";
7
7
  import { shader_particle_avboit_occupancy } from "./shaders/shader_particle_avboit_occupancy.js";
8
8
  import {
9
- PARTICLE_AVBOIT_DRAW_PIPELINE,
9
+ PARTICLE_AVBOIT_DRAW_ALPHA_PIPELINE,
10
+ PARTICLE_AVBOIT_DRAW_ADDITIVE_PIPELINE,
11
+ PARTICLE_AVBOIT_DRAW_GENERAL_PIPELINE,
10
12
  PARTICLE_AVBOIT_DRAW_RESOURCES,
11
13
  } from "./shaders/shader_particle_avboit_draw.js";
12
14
  import {
@@ -268,7 +270,7 @@ export function graph_particle_avboit_draw({
268
270
  // allowed because the attachment is read-only.
269
271
  const pass_encoder = encoder.constructRenderPass({
270
272
  label: "Particle AVBOIT Draw",
271
- pipeline: PARTICLE_AVBOIT_DRAW_PIPELINE,
273
+ pipeline: PARTICLE_AVBOIT_DRAW_ALPHA_PIPELINE,
272
274
  bindings,
273
275
  colorAttachments: [
274
276
  attachment(data.color),
@@ -281,8 +283,15 @@ export function graph_particle_avboit_draw({
281
283
  depthReadOnly: true,
282
284
  },
283
285
  });
284
- pass_encoder.drawIndirect(resources.get(data.draw_args), 0);
285
- pass_encoder.end();
286
+ // All three use the alive list; their vertex stages select disjoint particle groups.
287
+ // Premultiplied particles can both cover and emit, so they retain the general pipeline.
288
+ const args = resources.get(data.draw_args);
289
+ pass_encoder.drawIndirect(args, 0);
290
+ pass_encoder.setPipeline(graphics.render_pipelines.obtain(PARTICLE_AVBOIT_DRAW_ADDITIVE_PIPELINE));
291
+ pass_encoder.drawIndirect(args, 0);
292
+ pass_encoder.setPipeline(graphics.render_pipelines.obtain(PARTICLE_AVBOIT_DRAW_GENERAL_PIPELINE));
293
+ pass_encoder.drawIndirect(args, 0);
294
+ pass_encoder.end();
286
295
  }
287
296
 
288
297
  const pass_data = {};