@woosh/meep-engine 3.14.3 → 3.14.5

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 (137) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts +30 -0
  3. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts.map +1 -0
  4. package/src/core/geom/3d/mat4/m4_rigidity_defect.js +71 -0
  5. package/src/engine/Engine.d.ts.map +1 -1
  6. package/src/engine/Engine.js +733 -726
  7. package/src/engine/network/NetworkSession.d.ts +24 -4
  8. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  9. package/src/engine/network/NetworkSession.js +28 -4
  10. package/src/engine/network/orchestrator/NetworkPeer.d.ts +96 -7
  11. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  12. package/src/engine/network/orchestrator/NetworkPeer.js +392 -68
  13. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +6 -1
  14. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  15. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +6 -0
  16. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +105 -13
  17. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  18. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +306 -59
  19. package/src/engine/network/replication/Replicator.d.ts +43 -7
  20. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  21. package/src/engine/network/replication/Replicator.js +309 -111
  22. package/src/engine/network/sim/ActionLog.d.ts +23 -3
  23. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  24. package/src/engine/network/sim/ActionLog.js +26 -3
  25. package/src/engine/network/sim/ActionRecordCursor.d.ts +82 -0
  26. package/src/engine/network/sim/ActionRecordCursor.d.ts.map +1 -0
  27. package/src/engine/network/sim/ActionRecordCursor.js +120 -0
  28. package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
  29. package/src/engine/network/sim/RewindEngine.js +13 -29
  30. package/src/engine/network/sim/SimAction.d.ts +15 -0
  31. package/src/engine/network/sim/SimAction.d.ts.map +1 -1
  32. package/src/engine/network/sim/SimAction.js +15 -0
  33. package/src/engine/network/sim/SimActionExecutor.d.ts +51 -2
  34. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  35. package/src/engine/network/sim/SimActionExecutor.js +74 -7
  36. package/src/engine/network/transport/Channel.d.ts +1 -1
  37. package/src/engine/network/transport/Channel.js +1 -1
  38. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  39. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  40. package/src/engine/simulation/Ticker.js +4 -1
  41. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  42. package/src/engine/ui/GUIEngine.js +6 -0
  43. package/src/shade/playground/particle_system/README.md +98 -0
  44. package/src/shade/playground/particle_system/index.html +124 -0
  45. package/src/shade/playground/particle_system/main.d.ts +2 -0
  46. package/src/shade/playground/particle_system/main.d.ts.map +1 -0
  47. package/src/shade/playground/particle_system/main.js +180 -0
  48. package/src/shade/playground/particle_system/particle_scene.d.ts +52 -0
  49. package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -0
  50. package/src/shade/playground/particle_system/particle_scene.js +553 -0
  51. package/src/shade/playground/skinned_mesh_soup/README.md +143 -0
  52. package/src/shade/playground/skinned_mesh_soup/index.html +202 -0
  53. package/src/shade/playground/skinned_mesh_soup/main.d.ts +7 -0
  54. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -0
  55. package/src/shade/playground/skinned_mesh_soup/main.js +567 -0
  56. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +29 -0
  57. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -0
  58. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +428 -0
  59. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  60. package/src/shade/renderer/animation/GPUAnimationManager.js +1958 -1796
  61. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +357 -0
  62. package/src/shade/renderer/animation/skin_bind_transform.d.ts +25 -0
  63. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +1 -0
  64. package/src/shade/renderer/animation/skin_bind_transform.js +33 -0
  65. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +67 -0
  66. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -0
  67. package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +199 -0
  68. package/src/shade/renderer/animation/skin_frame_defect.d.ts +34 -0
  69. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +1 -0
  70. package/src/shade/renderer/animation/skin_frame_defect.js +132 -0
  71. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +24 -0
  72. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +1 -0
  73. package/src/shade/renderer/animation/skin_joint_matrix.js +46 -0
  74. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +70 -0
  75. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +1 -0
  76. package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +216 -0
  77. package/src/shade/renderer/animation/skin_test_fixtures.d.ts +86 -0
  78. package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +1 -0
  79. package/src/shade/renderer/animation/skin_test_fixtures.js +250 -0
  80. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +18 -4
  81. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts.map +1 -1
  82. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +18 -4
  83. package/src/shade/renderer/particles/DESIGN.md +5 -5
  84. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +1 -1
  85. package/src/shade/renderer/particles/cull/chunk_particle_cull.js +3 -1
  86. package/src/shade/renderer/particles/data/chunk_particle_emitter.js +1 -1
  87. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +43 -0
  88. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -0
  89. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +44 -0
  90. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +33 -59
  91. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  92. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +185 -118
  93. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +15 -10
  94. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  95. package/src/shade/renderer/particles/graph/compile_particle_graph.js +205 -170
  96. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +44 -0
  97. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -0
  98. package/src/shade/renderer/particles/graph/particle_graph_authoring.js +108 -0
  99. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +89 -0
  100. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -0
  101. package/src/shade/renderer/particles/isa/InstructionStream.js +162 -0
  102. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +1 -48
  103. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
  104. package/src/shade/renderer/particles/isa/ParticleAssembler.js +25 -114
  105. package/src/shade/renderer/particles/particle_prototype.d.ts.map +1 -1
  106. package/src/shade/renderer/particles/particle_prototype.js +181 -180
  107. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +4 -4
  108. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  109. package/src/shade/renderer/particles/runtime/create_particle_effect.js +19 -19
  110. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +1 -1
  111. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +4 -2
  112. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +4 -4
  113. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  114. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +7 -5
  115. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
  116. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +3 -1
  117. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  118. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +3 -1
  119. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  120. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +5 -2
  121. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  122. package/src/shade/renderer/particles/shaders/shader_particle_render.js +9 -2
  123. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  124. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -1
  125. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +1 -1
  126. package/src/shade/renderer/scene/SkinnedMesh.d.ts +11 -0
  127. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
  128. package/src/shade/renderer/scene/SkinnedMesh.js +11 -0
  129. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts +33 -3
  130. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts.map +1 -1
  131. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.js +58 -37
  132. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts +0 -54
  133. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts.map +0 -1
  134. package/src/shade/renderer/particles/graph/ParticleGraph.js +0 -99
  135. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts +0 -2
  136. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts.map +0 -1
  137. package/src/shade/renderer/particles/prototypeParticleSystem.js +0 -448
@@ -1,170 +1,205 @@
1
- import { PARTICLE_VM_REGISTER_COUNT } from "../ParticleConstants.js";
2
- import { ParticleAssembler } from "../isa/ParticleAssembler.js";
3
- import { PARTICLE_NODE_TYPES } from "./ParticleNodeRegistry.js";
4
-
5
- /**
6
- * Lower a single-phase {@link ../graph/ParticleGraph.js} into a VM instruction stream.
7
- *
8
- * The pipeline:
9
- * 1. Walk the graph from its sinks (attribute writes / kill), flattening it into an SSA-style value
10
- * list in dependency order. Literal input values are materialised as constant-load values, and
11
- * shared sub-expressions are evaluated once (DAG sharing via memoisation).
12
- * 2. Compute each value's last use.
13
- * 3. Linear-scan register allocation: registers are returned to the pool at a value's last use, so a
14
- * result can reuse a just-consumed input register (VM ops read all sources before writing dst).
15
- * 4. Emit each value's opcode via its node definition.
16
- *
17
- * @param {import("./ParticleGraph.js").ParticleGraph} graph
18
- * @param {import("../isa/ParticleAssembler.js").InstructionStream} stream target section
19
- * @param {ParticleAssembler} asm assembler owning the shared constant pool
20
- * @param {import("../layout/ParticleLayout.js").ParticleLayout} layout attribute schema
21
- */
22
- export function compile_particle_graph(graph, stream, asm, layout) {
23
- const values = flatten(graph);
24
- const last_use = compute_last_use(values);
25
-
26
- const register_of = new Array(values.length).fill(-1);
27
- const free = [];
28
- for (let r = PARTICLE_VM_REGISTER_COUNT - 1; r >= 0; r--) {
29
- free.push(r);
30
- }
31
-
32
- for (let i = 0; i < values.length; i++) {
33
- const value = values[i];
34
- const definition = PARTICLE_NODE_TYPES[value.type];
35
-
36
- // Capture the current registers of all inputs before we recycle any of them.
37
- const input_registers = {};
38
- for (const port of Object.keys(value.inputs)) {
39
- input_registers[port] = register_of[value.inputs[port]];
40
- }
41
-
42
- // Return registers whose owning value dies at this instruction (its last consumer).
43
- const freed = new Set();
44
- for (const port of Object.keys(value.inputs)) {
45
- const source = value.inputs[port];
46
- if (last_use[source] === i && register_of[source] >= 0 && !freed.has(source)) {
47
- free.push(register_of[source]);
48
- register_of[source] = -1;
49
- freed.add(source);
50
- }
51
- }
52
-
53
- let out;
54
- if (definition.produces_value) {
55
- if (free.length === 0) {
56
- throw new Error(
57
- `Particle graph exceeds the VM register file (${PARTICLE_VM_REGISTER_COUNT} registers). ` +
58
- `Simplify the effect or split it.`
59
- );
60
- }
61
- out = free.pop();
62
- register_of[i] = out;
63
- }
64
-
65
- definition.emit(stream, {
66
- in: (port) => input_registers[port],
67
- out,
68
- params: value.params,
69
- layout,
70
- asm,
71
- });
72
- }
73
- }
74
-
75
- /**
76
- * Compile a full effect (SPAWN + UPDATE) into a {@link ../isa/ParticleProgram.js}.
77
- *
78
- * @param {object} params
79
- * @param {import("./ParticleGraph.js").ParticleGraph} [params.spawn]
80
- * @param {import("./ParticleGraph.js").ParticleGraph} [params.update]
81
- * @param {import("../layout/ParticleLayout.js").ParticleLayout} params.layout
82
- * @returns {import("../isa/ParticleProgram.js").ParticleProgram}
83
- */
84
- export function compile_particle_effect({ spawn, update, layout }) {
85
- const asm = new ParticleAssembler();
86
- if (spawn !== undefined) {
87
- compile_particle_graph(spawn, asm.spawn, asm, layout);
88
- }
89
- if (update !== undefined) {
90
- compile_particle_graph(update, asm.update, asm, layout);
91
- }
92
- const program = asm.build();
93
- program.validate();
94
- return program;
95
- }
96
-
97
- /**
98
- * Flatten a graph into a dependency-ordered value list. Each value is
99
- * `{ type, params, inputs: { port -> value_index }, sink }`. The array order is a valid topological
100
- * order (every input has a lower index).
101
- * @param {import("./ParticleGraph.js").ParticleGraph} graph
102
- * @returns {Array<{type: string, params: object, inputs: object, sink: boolean}>}
103
- */
104
- function flatten(graph) {
105
- const values = [];
106
- const node_to_value = new Map();
107
- const visiting = new Set();
108
-
109
- function visit(node_id) {
110
- if (node_to_value.has(node_id)) {
111
- return node_to_value.get(node_id);
112
- }
113
- if (visiting.has(node_id)) {
114
- throw new Error(`Cycle detected in particle graph at node ${node_id}`);
115
- }
116
- visiting.add(node_id);
117
-
118
- const node = graph.nodes.get(node_id);
119
- if (node === undefined) {
120
- throw new Error(`Particle graph references missing node ${node_id}`);
121
- }
122
- const definition = PARTICLE_NODE_TYPES[node.type];
123
-
124
- const inputs = {};
125
- for (const port of definition.inputs) {
126
- const source = node.inputs.get(port);
127
- if (source === undefined) {
128
- throw new Error(`Node ${node_id} (${node.type}) is missing a binding for input '${port}'`);
129
- }
130
- if (source.kind === "literal") {
131
- const vid = values.length;
132
- values.push({ type: "const", params: { value: source.value }, inputs: {}, sink: false });
133
- inputs[port] = vid;
134
- } else {
135
- inputs[port] = visit(source.node);
136
- }
137
- }
138
-
139
- const vid = values.length;
140
- values.push({ type: node.type, params: node.params, inputs, sink: definition.is_sink });
141
- node_to_value.set(node_id, vid);
142
- visiting.delete(node_id);
143
- return vid;
144
- }
145
-
146
- for (const [id, node] of graph.nodes) {
147
- if (PARTICLE_NODE_TYPES[node.type].is_sink) {
148
- visit(id);
149
- }
150
- }
151
-
152
- return values;
153
- }
154
-
155
- /**
156
- * @param {Array} values
157
- * @returns {number[]} last_use[i] = highest index that consumes value i (or -1 if never consumed)
158
- */
159
- function compute_last_use(values) {
160
- const last_use = new Array(values.length).fill(-1);
161
- for (let i = 0; i < values.length; i++) {
162
- for (const port of Object.keys(values[i].inputs)) {
163
- const source = values[i].inputs[port];
164
- if (i > last_use[source]) {
165
- last_use[source] = i;
166
- }
167
- }
168
- }
169
- return last_use;
170
- }
1
+ import { PARTICLE_VM_REGISTER_COUNT } from "../ParticleConstants.js";
2
+ import { ParticleAssembler } from "../isa/ParticleAssembler.js";
3
+
4
+ /**
5
+ * Lower a single-phase particle {@link ../../../../core/model/node-graph/NodeGraph.js NodeGraph}
6
+ * into a VM instruction stream.
7
+ *
8
+ * The graph describes one lifecycle phase (SPAWN or UPDATE) of a particle effect: nodes are
9
+ * {@link ParticleNodeDescription} instances, and every connection wires a producing node's `out`
10
+ * port to a consuming node's named input port.
11
+ *
12
+ * The pipeline:
13
+ * 1. Walk the graph from its sinks (attribute writes / kill), flattening it into an SSA-style value
14
+ * list in dependency order. Shared sub-expressions are evaluated once (DAG sharing via
15
+ * memoisation).
16
+ * 2. Compute each value's last use.
17
+ * 3. Linear-scan register allocation: registers are returned to the pool at a value's last use, so a
18
+ * result can reuse a just-consumed input register (VM ops read all sources before writing dst).
19
+ * 4. Emit each value's opcode via its node description.
20
+ *
21
+ * @param {NodeGraph} graph
22
+ * @param {import("../isa/ParticleAssembler.js").InstructionStream} stream target section
23
+ * @param {ParticleAssembler} asm assembler owning the shared constant pool
24
+ * @param {import("../layout/ParticleLayout.js").ParticleLayout} layout attribute schema
25
+ */
26
+ export function compile_particle_graph(graph, stream, asm, layout) {
27
+ const values = flatten(graph);
28
+ const last_use = compute_last_use(values);
29
+
30
+ const register_of = new Array(values.length).fill(-1);
31
+ const free = [];
32
+ for (let r = PARTICLE_VM_REGISTER_COUNT - 1; r >= 0; r--) {
33
+ free.push(r);
34
+ }
35
+
36
+ for (let i = 0; i < values.length; i++) {
37
+ const value = values[i];
38
+ const description = value.description;
39
+
40
+ // Capture the current registers of all inputs before we recycle any of them.
41
+ const input_registers = {};
42
+ for (const port of Object.keys(value.inputs)) {
43
+ input_registers[port] = register_of[value.inputs[port]];
44
+ }
45
+
46
+ // Return registers whose owning value dies at this instruction (its last consumer).
47
+ const freed = new Set();
48
+ for (const port of Object.keys(value.inputs)) {
49
+ const source = value.inputs[port];
50
+ if (last_use[source] === i && register_of[source] >= 0 && !freed.has(source)) {
51
+ free.push(register_of[source]);
52
+ register_of[source] = -1;
53
+ freed.add(source);
54
+ }
55
+ }
56
+
57
+ let out;
58
+ if (!description.is_sink) {
59
+ if (free.length === 0) {
60
+ throw new Error(
61
+ `Particle graph exceeds the VM register file (${PARTICLE_VM_REGISTER_COUNT} registers). ` +
62
+ `Simplify the effect or split it.`
63
+ );
64
+ }
65
+ out = free.pop();
66
+ register_of[i] = out;
67
+ }
68
+
69
+ description.emit(stream, {
70
+ in: (port) => input_registers[port],
71
+ out,
72
+ parameters: value.parameters,
73
+ layout,
74
+ asm,
75
+ });
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Compile a full effect (SPAWN + UPDATE) into a {@link ../isa/ParticleProgram.js}.
81
+ *
82
+ * @param {object} params
83
+ * @param {NodeGraph} [params.spawn]
84
+ * @param {NodeGraph} [params.update]
85
+ * @param {import("../layout/ParticleLayout.js").ParticleLayout} params.layout
86
+ * @returns {import("../isa/ParticleProgram.js").ParticleProgram}
87
+ */
88
+ export function compile_particle_effect({ spawn, update, layout }) {
89
+ const asm = new ParticleAssembler();
90
+ if (spawn !== undefined) {
91
+ compile_particle_graph(spawn, asm.spawn, asm, layout);
92
+ }
93
+ if (update !== undefined) {
94
+ compile_particle_graph(update, asm.update, asm, layout);
95
+ }
96
+ const program = asm.build();
97
+ program.validate();
98
+ return program;
99
+ }
100
+
101
+ /**
102
+ * Flatten a graph into a dependency-ordered value list. Each value is
103
+ * `{ description, parameters, inputs: { port_name -> value_index } }`. The array order is a valid
104
+ * topological order (every input has a lower index).
105
+ * @param {NodeGraph} graph
106
+ * @returns {Array<{description: ParticleNodeDescription, parameters: object, inputs: object}>}
107
+ */
108
+ function flatten(graph) {
109
+ const values = [];
110
+ const node_to_value = new Map();
111
+ const visiting = new Set();
112
+
113
+ /**
114
+ * @param {NodeInstance} node
115
+ * @returns {number} index of the node's value
116
+ */
117
+ function visit(node) {
118
+ const existing = node_to_value.get(node.id);
119
+ if (existing !== undefined) {
120
+ return existing;
121
+ }
122
+ if (visiting.has(node.id)) {
123
+ throw new Error(`Cycle detected in particle graph at node ${node.id}`);
124
+ }
125
+ visiting.add(node.id);
126
+
127
+ const description = require_particle_description(node);
128
+
129
+ const inputs = {};
130
+ for (const port of description.inPorts) {
131
+ inputs[port.name] = visit(resolve_input(node, port));
132
+ }
133
+
134
+ const vid = values.length;
135
+ values.push({ description, parameters: node.parameters, inputs });
136
+ node_to_value.set(node.id, vid);
137
+ visiting.delete(node.id);
138
+ return vid;
139
+ }
140
+
141
+ for (const node of graph.getNodes()) {
142
+ if (require_particle_description(node).is_sink) {
143
+ visit(node);
144
+ }
145
+ }
146
+
147
+ return values;
148
+ }
149
+
150
+ /**
151
+ * @param {NodeInstance} node
152
+ * @returns {ParticleNodeDescription}
153
+ */
154
+ function require_particle_description(node) {
155
+ const description = node.description;
156
+
157
+ if (description === null || description === undefined || description.isParticleNodeDescription !== true) {
158
+ throw new Error(`Node ${node.id} (${description}) is not a particle node, it cannot be compiled`);
159
+ }
160
+
161
+ return description;
162
+ }
163
+
164
+ /**
165
+ * Find the node driving one input port of a node.
166
+ * @param {NodeInstance} node
167
+ * @param {Port} port an input port of the node's description
168
+ * @returns {NodeInstance}
169
+ * @throws if the port has no source, or more than one
170
+ */
171
+ function resolve_input(node, port) {
172
+ const endpoint = node.getEndpoint(port.id);
173
+
174
+ const connections = endpoint.inConnections;
175
+
176
+ if (connections.length === 0) {
177
+ throw new Error(`Node ${node.id} (${node.description.name}) is missing a binding for input '${port.name}'`);
178
+ }
179
+
180
+ if (connections.length > 1) {
181
+ throw new Error(
182
+ `Node ${node.id} (${node.description.name}) has ${connections.length} connections into input '${port.name}', ` +
183
+ `an input may have at most one`
184
+ );
185
+ }
186
+
187
+ return connections[0].source.instance;
188
+ }
189
+
190
+ /**
191
+ * @param {Array} values
192
+ * @returns {number[]} last_use[i] = highest index that consumes value i (or -1 if never consumed)
193
+ */
194
+ function compute_last_use(values) {
195
+ const last_use = new Array(values.length).fill(-1);
196
+ for (let i = 0; i < values.length; i++) {
197
+ for (const port of Object.keys(values[i].inputs)) {
198
+ const source = values[i].inputs[port];
199
+ if (i > last_use[source]) {
200
+ last_use[source] = i;
201
+ }
202
+ }
203
+ }
204
+ return last_use;
205
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Authoring sugar for building particle effects on a plain
3
+ * {@link ../../../../core/model/node-graph/NodeGraph.js NodeGraph}.
4
+ *
5
+ * These are convenience wrappers only — the graph, its nodes and its connections are the standard
6
+ * node-graph model, so the editor, serializers and graph utilities all work on particle effects
7
+ * unchanged. Everything downstream (see {@link ./compile_particle_graph.js}) consumes `NodeGraph`
8
+ * directly and never sees these helpers.
9
+ */
10
+ /**
11
+ * Add a node to a particle graph.
12
+ *
13
+ * @param {NodeGraph} graph
14
+ * @param {string} type a node type name registered in {@link ./ParticleNodeRegistry.js}
15
+ * @param {object} [parameters] static parameters (attribute name, builtin id, constant value, …)
16
+ * @returns {number} the new node instance id
17
+ */
18
+ export function particle_node(graph: NodeGraph, type: string, parameters?: object): number;
19
+ /**
20
+ * Bind an input port of a node to a source, replacing whatever was bound to it before.
21
+ *
22
+ * The source may be:
23
+ * - a node id (number) → that node's `out` port,
24
+ * - `{ node, port }` → an explicit port,
25
+ * - a number array → a literal `vec4`, materialised as a `const` node (scalar broadcast is NOT
26
+ * applied; `[5]` means `[5,0,0,0]`).
27
+ *
28
+ * @param {NodeGraph} graph
29
+ * @param {number} node_id
30
+ * @param {string} port input port name
31
+ * @param {number|number[]|{node: number, port?: string}} source
32
+ * @returns {number} connection id
33
+ */
34
+ export function particle_wire(graph: NodeGraph, node_id: number, port: string, source: number | number[] | {
35
+ node: number;
36
+ port?: string;
37
+ }): number;
38
+ /**
39
+ * Wrap a scalar or vector as a literal source (so it is unambiguous vs. a node id).
40
+ * @param {...number} components
41
+ * @returns {number[]}
42
+ */
43
+ export function literal(...components: number[]): number[];
44
+ //# sourceMappingURL=particle_graph_authoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"particle_graph_authoring.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/particle_graph_authoring.js"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,sDAJW,MAAM,eACN,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;;;;;;GAcG;AACH,yDALW,MAAM,QACN,MAAM;UACiB,MAAM;WAAS,MAAM;IAC1C,MAAM,CA2BlB;AA6BD;;;;GAIG;AACH,uCAHc,MAAM,KACP,MAAM,EAAE,CAIpB"}
@@ -0,0 +1,108 @@
1
+ import { PortDirection } from "../../../../core/model/node-graph/node/PortDirection.js";
2
+ import { get_particle_node_description, PARTICLE_NODE_OUTPUT_PORT } from "./ParticleNodeRegistry.js";
3
+
4
+ /**
5
+ * Authoring sugar for building particle effects on a plain
6
+ * {@link ../../../../core/model/node-graph/NodeGraph.js NodeGraph}.
7
+ *
8
+ * These are convenience wrappers only — the graph, its nodes and its connections are the standard
9
+ * node-graph model, so the editor, serializers and graph utilities all work on particle effects
10
+ * unchanged. Everything downstream (see {@link ./compile_particle_graph.js}) consumes `NodeGraph`
11
+ * directly and never sees these helpers.
12
+ */
13
+
14
+ /**
15
+ * Add a node to a particle graph.
16
+ *
17
+ * @param {NodeGraph} graph
18
+ * @param {string} type a node type name registered in {@link ./ParticleNodeRegistry.js}
19
+ * @param {object} [parameters] static parameters (attribute name, builtin id, constant value, …)
20
+ * @returns {number} the new node instance id
21
+ */
22
+ export function particle_node(graph, type, parameters = {}) {
23
+ const description = get_particle_node_description(type);
24
+
25
+ const id = graph.createNode(description);
26
+
27
+ graph.getNodeSafe(id).setParameters(parameters);
28
+
29
+ return id;
30
+ }
31
+
32
+ /**
33
+ * Bind an input port of a node to a source, replacing whatever was bound to it before.
34
+ *
35
+ * The source may be:
36
+ * - a node id (number) → that node's `out` port,
37
+ * - `{ node, port }` → an explicit port,
38
+ * - a number array → a literal `vec4`, materialised as a `const` node (scalar broadcast is NOT
39
+ * applied; `[5]` means `[5,0,0,0]`).
40
+ *
41
+ * @param {NodeGraph} graph
42
+ * @param {number} node_id
43
+ * @param {string} port input port name
44
+ * @param {number|number[]|{node: number, port?: string}} source
45
+ * @returns {number} connection id
46
+ */
47
+ export function particle_wire(graph, node_id, port, source) {
48
+ const node = graph.getNodeSafe(node_id);
49
+
50
+ const port_object = node.description.findPortByNameAndDirection(port, PortDirection.In);
51
+
52
+ if (port_object === undefined) {
53
+ throw new Error(`Node ${node_id} (${node.description.name}) has no input port '${port}'`);
54
+ }
55
+
56
+ // snapshot before connecting, so the replacement is only committed once the new wiring holds
57
+ const previous = node.getEndpoint(port_object.id).inConnections;
58
+
59
+ const resolved = resolve_source(graph, source);
60
+
61
+ const connection_id = graph.createConnectionByPortName(
62
+ resolved.node, resolved.port,
63
+ node_id, port
64
+ );
65
+
66
+ // an input may have at most one source, so binding it again replaces rather than adds
67
+ for (const connection of previous) {
68
+ graph.deleteConnection(connection.id);
69
+ }
70
+
71
+ return connection_id;
72
+ }
73
+
74
+ /**
75
+ * @param {NodeGraph} graph
76
+ * @param {number|number[]|{node: number, port?: string}} source
77
+ * @returns {{node: number, port: string}}
78
+ */
79
+ function resolve_source(graph, source) {
80
+ if (typeof source === "number") {
81
+ // Could be a node id or a scalar literal — ambiguous, so literals must use the array form.
82
+ return { node: source, port: PARTICLE_NODE_OUTPUT_PORT };
83
+ }
84
+
85
+ if (Array.isArray(source)) {
86
+ const value = [source[0] ?? 0, source[1] ?? 0, source[2] ?? 0, source[3] ?? 0];
87
+
88
+ return {
89
+ node: particle_node(graph, "const", { value }),
90
+ port: PARTICLE_NODE_OUTPUT_PORT
91
+ };
92
+ }
93
+
94
+ if (source !== null && typeof source === "object" && typeof source.node === "number") {
95
+ return { node: source.node, port: source.port ?? PARTICLE_NODE_OUTPUT_PORT };
96
+ }
97
+
98
+ throw new Error(`Invalid input source: ${JSON.stringify(source)}`);
99
+ }
100
+
101
+ /**
102
+ * Wrap a scalar or vector as a literal source (so it is unambiguous vs. a node id).
103
+ * @param {...number} components
104
+ * @returns {number[]}
105
+ */
106
+ export function literal(...components) {
107
+ return components;
108
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A single VM instruction stream (a SPAWN or UPDATE section). Instructions are appended in order;
3
+ * {@link toWords} flattens them to the packed `Uint32Array` the program consumes.
4
+ *
5
+ * Register usage is tracked as instructions are emitted so the assembler can report how many
6
+ * registers the program touches without a separate pass.
7
+ */
8
+ export class InstructionStream {
9
+ /** @type {number[][]} */
10
+ instructions: number[][];
11
+ /** @type {number} highest register index referenced, plus one */
12
+ register_count: number;
13
+ /**
14
+ * Emit a raw instruction. Validates the opcode and tracks register usage. Prefer the typed
15
+ * helpers below where they exist — they document the non-obvious operand packing.
16
+ *
17
+ * @param {number} op
18
+ * @param {number} [dst]
19
+ * @param {number} [s0]
20
+ * @param {number} [s1]
21
+ * @param {number} [s2]
22
+ * @returns {InstructionStream} this, for chaining
23
+ */
24
+ emit(op: number, dst?: number, s0?: number, s1?: number, s2?: number): InstructionStream;
25
+ /**
26
+ * @param {number} dst
27
+ * @param {number} src
28
+ * @returns {InstructionStream}
29
+ */
30
+ mov(dst: number, src: number): InstructionStream;
31
+ /**
32
+ * @param {number} dst
33
+ * @param {number} constant_index
34
+ * @returns {InstructionStream}
35
+ */
36
+ loadConst(dst: number, constant_index: number): InstructionStream;
37
+ /**
38
+ * @param {number} dst
39
+ * @param {number} word_offset
40
+ * @param {number} component_count
41
+ * @returns {InstructionStream}
42
+ */
43
+ loadAttr(dst: number, word_offset: number, component_count: number): InstructionStream;
44
+ /**
45
+ * @param {number} src_register
46
+ * @param {number} word_offset
47
+ * @param {number} component_count
48
+ * @returns {InstructionStream}
49
+ */
50
+ storeAttr(src_register: number, word_offset: number, component_count: number): InstructionStream;
51
+ /**
52
+ * @param {number} dst
53
+ * @param {number} builtin_id
54
+ * @returns {InstructionStream}
55
+ */
56
+ loadBuiltin(dst: number, builtin_id: number): InstructionStream;
57
+ /**
58
+ * @param {number} condition_register
59
+ * @returns {InstructionStream}
60
+ */
61
+ kill(condition_register: number): InstructionStream;
62
+ /**
63
+ * @param {number} dst
64
+ * @param {number} mode
65
+ * @returns {InstructionStream}
66
+ */
67
+ random(dst: number, mode: number): InstructionStream;
68
+ /**
69
+ * @param {number} dst
70
+ * @param {number} curve_handle
71
+ * @param {number} t_register
72
+ * @returns {InstructionStream}
73
+ */
74
+ curve(dst: number, curve_handle: number, t_register: number): InstructionStream;
75
+ /**
76
+ * @returns {InstructionStream}
77
+ */
78
+ halt(): InstructionStream;
79
+ /**
80
+ * @returns {number}
81
+ */
82
+ get instruction_count(): number;
83
+ /**
84
+ * @returns {Uint32Array}
85
+ */
86
+ toWords(): Uint32Array;
87
+ #private;
88
+ }
89
+ //# sourceMappingURL=InstructionStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InstructionStream.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/isa/InstructionStream.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH;IACI,yBAAyB;IACzB,cADW,MAAM,EAAE,EAAE,CACH;IAClB,iEAAiE;IACjE,gBADW,MAAM,CACE;IAUnB;;;;;;;;;;OAUG;IACH,SAPW,MAAM,QACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,GACJ,iBAAiB,CAsB7B;IAID;;;;OAIG;IACH,SAJW,MAAM,OACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;OAIG;IACH,eAJW,MAAM,kBACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;;OAKG;IACH,cALW,MAAM,eACN,MAAM,mBACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;;OAKG;IACH,wBALW,MAAM,eACN,MAAM,mBACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;OAIG;IACH,iBAJW,MAAM,cACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;OAGG;IACH,yBAHW,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;OAIG;IACH,YAJW,MAAM,QACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;;OAKG;IACH,WALW,MAAM,gBACN,MAAM,cACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;OAEG;IACH,QAFa,iBAAiB,CAI7B;IAED;;OAEG;IACH,gCAEC;IAED;;OAEG;IACH,WAFa,WAAW,CAWvB;;CACJ"}