@field/cascade 0.4.0 → 0.5.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.
- package/AGENTS.md +18 -0
- package/ARCHITECTURE.md +53 -7
- package/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +33 -0
- package/DESIGN.md +9 -6
- package/README.md +80 -4
- package/dist/assets/{CodeEditor-BNOJIDzL.js → CodeEditor-dOrU3aQg.js} +7 -7
- package/dist/assets/{CodePanel-2wp2G0Fj.js → CodePanel-Bqy85qen.js} +2 -2
- package/dist/assets/{CustomNodeDialog-DNqaVBqA.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
- package/dist/assets/{ProjectSettings-DcdYF2E1.js → ProjectSettings-D192mr6g.js} +2 -2
- package/dist/assets/{SettingsDialog-D2xVvyiR.js → SettingsDialog-BTeeIE3T.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DW288ykF.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
- package/dist/assets/{core-B1y38i95.js → core-DwbcGL0o.js} +2 -2
- package/dist/assets/{cssMode-BLucnaln.js → cssMode-Dcjn356x.js} +1 -1
- package/dist/assets/definitions-C_SY6Bhg.js +1 -0
- package/dist/assets/{editor.api-B3Et3Pph.js → editor.api-D7Q83606.js} +1 -1
- package/dist/assets/{freemarker2-DVlPoano.js → freemarker2-DCCiHlV6.js} +1 -1
- package/dist/assets/geo-CT-wtvaE.js +1 -0
- package/dist/assets/{handlebars-B5FZBAMR.js → handlebars-BCxC8xu1.js} +1 -1
- package/dist/assets/{html-FodD8KbR.js → html-COwepzSd.js} +1 -1
- package/dist/assets/{htmlMode-CsG57VCD.js → htmlMode-Dpg7H0rx.js} +1 -1
- package/dist/assets/{image-B3F-nq25.js → image-CMfMMxlC.js} +3 -3
- package/dist/assets/index-De1cIZ9N.js +212 -0
- package/dist/assets/{index-Y2ock1cw.css → index-ilgbOu6G.css} +1 -1
- package/dist/assets/{javascript-B3gonF_H.js → javascript-CSwWxfLr.js} +1 -1
- package/dist/assets/{jsonMode-Y3w1ahZg.js → jsonMode-BXRriTpj.js} +1 -1
- package/dist/assets/{liquid-CTpWXIYO.js → liquid-Cmkfyogc.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-CODBdKEZ.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
- package/dist/assets/{mdx-CDAQJZIz.js → mdx-BzrNjNvK.js} +1 -1
- package/dist/assets/nodeTypeUtils-BDJsc2Ks.js +1 -0
- package/dist/assets/pop-BdKEHslJ.js +1 -0
- package/dist/assets/{python-LOIlxJU-.js → python-D4E9jbx7.js} +1 -1
- package/dist/assets/{razor-CllPQSHo.js → razor-B3x9cIEM.js} +1 -1
- package/dist/assets/{tsMode-CO28ut8t.js → tsMode-dhQYPNMZ.js} +1 -1
- package/dist/assets/{typescript-ruU9wKQ_.js → typescript-CITkDxAE.js} +1 -1
- package/dist/assets/{workers-Bc1Ywp4-.js → workers-BQvF9bbO.js} +1 -1
- package/dist/assets/{xml-DoXP2Dh1.js → xml-DvHlU3A8.js} +1 -1
- package/dist/assets/{yaml-D9YPyGiJ.js → yaml-uznsLgs8.js} +1 -1
- package/dist/cli/index.js +16227 -12672
- package/dist/cli/runtime/io.ts +31 -25
- package/dist/contracts/definition.d.ts +21 -2
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/particles.d.ts +54 -0
- package/dist/contracts/particles.js +167 -0
- package/dist/contracts/scene.d.ts +129 -0
- package/dist/contracts/scene.js +73 -0
- package/dist/contracts/schema.js +1 -1
- package/dist/contracts/validate.js +4 -2
- package/dist/contracts/values.d.ts +3 -2
- package/dist/contracts/values.js +9 -0
- package/dist/index.html +3 -3
- package/dist/io.d.ts +4 -16
- package/dist/io.js +26 -8
- package/dist/player/app.js +810 -0
- package/dist/player/embed.d.ts +4 -0
- package/dist/player/embed.js +77 -0
- package/dist/player/types.d.ts +35 -0
- package/dist/runtime/builtins/core/index.d.ts +3 -2
- package/dist/runtime/builtins/core/index.js +6 -2
- package/dist/runtime/builtins/core/structural.d.ts +168 -0
- package/dist/runtime/builtins/core/structural.js +175 -0
- package/dist/runtime/builtins/core/time.d.ts +165 -0
- package/dist/runtime/builtins/core/time.js +110 -0
- package/dist/runtime/builtins/geo/definitions.d.ts +247 -0
- package/dist/runtime/builtins/geo/definitions.js +70 -0
- package/dist/runtime/builtins/geo/index.d.ts +2 -1
- package/dist/runtime/builtins/geo/index.js +3 -1
- package/dist/runtime/builtins/geo/render.d.ts +87 -0
- package/dist/runtime/builtins/geo/render.js +87 -0
- package/dist/runtime/builtins/geo/svgExport.d.ts +11 -0
- package/dist/runtime/builtins/geo/svgExport.js +10 -0
- package/dist/runtime/builtins/index.d.ts +5 -2
- package/dist/runtime/builtins/index.js +13 -3
- package/dist/runtime/builtins/pop/definitions.d.ts +2120 -0
- package/dist/runtime/builtins/pop/definitions.js +815 -0
- package/dist/runtime/builtins/pop/index.d.ts +2 -0
- package/dist/runtime/builtins/pop/index.js +2 -0
- package/dist/runtime/builtins/scene/definitions.d.ts +356 -0
- package/dist/runtime/builtins/scene/definitions.js +224 -0
- package/dist/runtime/builtins/scene/index.d.ts +2 -0
- package/dist/runtime/builtins/scene/index.js +2 -0
- package/dist/runtime/geometry/render.d.ts +109 -0
- package/dist/runtime/geometry/render.js +145 -0
- package/dist/runtime/geometry/svg.d.ts +29 -0
- package/dist/runtime/geometry/svg.js +11 -5
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/pop/cache.d.ts +27 -0
- package/dist/runtime/pop/cache.js +123 -0
- package/dist/runtime/pop/forces.d.ts +202 -0
- package/dist/runtime/pop/forces.js +519 -0
- package/dist/runtime/pop/index.d.ts +5 -0
- package/dist/runtime/pop/index.js +5 -0
- package/dist/runtime/pop/state.d.ts +136 -0
- package/dist/runtime/pop/state.js +283 -0
- package/dist/runtime/pop/trail.d.ts +62 -0
- package/dist/runtime/pop/trail.js +142 -0
- package/dist/runtime/runtime.js +221 -3
- package/dist/runtime/scene/index.d.ts +50 -0
- package/dist/runtime/scene/index.js +94 -0
- package/dist/studio/panel.d.ts +68 -1
- package/doc/HEADLESS_GPU.md +4 -4
- package/doc/NODE_REFERENCE.md +372 -1
- package/doc/PROJECT_AUTHORING.md +16 -4
- package/doc/WEB_PLAYER.md +131 -0
- package/package.json +16 -2
- package/dist/assets/geo-DhyFdBXX.js +0 -1
- package/dist/assets/index-CC6YQw2n.js +0 -212
- package/dist/assets/nodeTypeUtils-BBA1JqHx.js +0 -1
|
@@ -0,0 +1,2120 @@
|
|
|
1
|
+
import { type NodeExecutionContext } from "@field/cascade/contracts";
|
|
2
|
+
import type { DefinitionNodeRegistration } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* `cascade.pop.*` - the minimum useful particle set.
|
|
5
|
+
*
|
|
6
|
+
* Step 2 of `PLAN particles.md`, and the step that carries the risk: a source,
|
|
7
|
+
* a solver, and forces, on **re-simulation only**. There is no cache. The plan
|
|
8
|
+
* argues for that order - re-simulation from the start frame is the definition
|
|
9
|
+
* of correctness, and a cache is an accelerator holding nothing re-simulation
|
|
10
|
+
* would not have produced, so it is built second and can be cleared at any
|
|
11
|
+
* time without changing a result.
|
|
12
|
+
*
|
|
13
|
+
* The arithmetic lives in `packages/runtime/src/pop`, deliberately: these
|
|
14
|
+
* nodes are thin, the risk is in the stepping, and the stepping is testable
|
|
15
|
+
* without a graph.
|
|
16
|
+
*/
|
|
17
|
+
declare const sourceDefinition: {
|
|
18
|
+
readonly apiVersion: 1;
|
|
19
|
+
readonly label: "POP Source";
|
|
20
|
+
readonly description: "Birth particles from a geometry's points.";
|
|
21
|
+
readonly icon: "Sparkles";
|
|
22
|
+
readonly runsOn: "portable";
|
|
23
|
+
readonly inputs: {
|
|
24
|
+
readonly geometry: {
|
|
25
|
+
readonly kind: "data";
|
|
26
|
+
readonly type: "geometry";
|
|
27
|
+
};
|
|
28
|
+
readonly particles: {
|
|
29
|
+
readonly kind: "data";
|
|
30
|
+
readonly type: "geometry";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
readonly outputs: {
|
|
34
|
+
readonly geometry: {
|
|
35
|
+
readonly kind: "data";
|
|
36
|
+
readonly type: "geometry";
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
readonly props: {
|
|
40
|
+
readonly impulse: {
|
|
41
|
+
readonly type: "int";
|
|
42
|
+
readonly default: 8;
|
|
43
|
+
readonly min: 0;
|
|
44
|
+
readonly max: 100000;
|
|
45
|
+
};
|
|
46
|
+
readonly life: {
|
|
47
|
+
readonly type: "float";
|
|
48
|
+
readonly default: 3;
|
|
49
|
+
readonly min: 0;
|
|
50
|
+
readonly max: 3600;
|
|
51
|
+
readonly step: 0.1;
|
|
52
|
+
};
|
|
53
|
+
readonly lifevar: {
|
|
54
|
+
readonly type: "float";
|
|
55
|
+
readonly default: 0.3;
|
|
56
|
+
readonly min: 0;
|
|
57
|
+
readonly max: 1;
|
|
58
|
+
readonly step: 0.01;
|
|
59
|
+
};
|
|
60
|
+
readonly velocity: {
|
|
61
|
+
readonly type: "vec2";
|
|
62
|
+
readonly default: readonly [0, 0];
|
|
63
|
+
};
|
|
64
|
+
readonly seed: {
|
|
65
|
+
readonly type: "int";
|
|
66
|
+
readonly default: 7;
|
|
67
|
+
readonly min: 0;
|
|
68
|
+
readonly max: 999999;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export declare function executeSource(context: NodeExecutionContext<typeof sourceDefinition>): void;
|
|
73
|
+
declare const solverDefinition: {
|
|
74
|
+
readonly apiVersion: 1;
|
|
75
|
+
readonly label: "POP Solver";
|
|
76
|
+
readonly description: "Advance a particle system by one timestep.";
|
|
77
|
+
readonly icon: "Play";
|
|
78
|
+
readonly runsOn: "portable";
|
|
79
|
+
readonly inputs: {
|
|
80
|
+
readonly particles: {
|
|
81
|
+
readonly kind: "data";
|
|
82
|
+
readonly type: "geometry";
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
readonly outputs: {
|
|
86
|
+
readonly geometry: {
|
|
87
|
+
readonly kind: "data";
|
|
88
|
+
readonly type: "geometry";
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
readonly props: {
|
|
92
|
+
/**
|
|
93
|
+
* Where the timestep comes from. Marcus's ruling, 2026-09-09: *"Let the
|
|
94
|
+
* user choose nodes fixed or variable."*
|
|
95
|
+
*
|
|
96
|
+
* `fixed` is the default on the ordinary grounds that the safer of two
|
|
97
|
+
* behaviours is the one you get without asking - a simulation whose result
|
|
98
|
+
* depends on a playback setting is not reproducible from the document
|
|
99
|
+
* alone. With `fps` the render rate becomes part of what determines the
|
|
100
|
+
* output, which is correct for a mode called variable and must not be
|
|
101
|
+
* silent: it belongs in the cache key, and `cascade check` should say so.
|
|
102
|
+
*/
|
|
103
|
+
readonly timestep_mode: {
|
|
104
|
+
readonly type: "string";
|
|
105
|
+
readonly default: "fixed";
|
|
106
|
+
readonly control: "select";
|
|
107
|
+
readonly options: readonly [{
|
|
108
|
+
readonly value: "fixed";
|
|
109
|
+
readonly label: "Fixed - reproducible from the document";
|
|
110
|
+
}, {
|
|
111
|
+
readonly value: "fps";
|
|
112
|
+
readonly label: "Variable - derived from the render rate";
|
|
113
|
+
}];
|
|
114
|
+
};
|
|
115
|
+
readonly timestep: {
|
|
116
|
+
readonly type: "float";
|
|
117
|
+
readonly default: number;
|
|
118
|
+
readonly min: number;
|
|
119
|
+
readonly max: 1;
|
|
120
|
+
readonly step: number;
|
|
121
|
+
};
|
|
122
|
+
readonly maxspeed: {
|
|
123
|
+
readonly type: "float";
|
|
124
|
+
readonly default: 0;
|
|
125
|
+
readonly min: 0;
|
|
126
|
+
readonly max: 10000;
|
|
127
|
+
readonly step: 0.5;
|
|
128
|
+
};
|
|
129
|
+
readonly force: {
|
|
130
|
+
readonly type: "vec2";
|
|
131
|
+
readonly default: readonly [0, 0];
|
|
132
|
+
};
|
|
133
|
+
readonly airresist: {
|
|
134
|
+
readonly type: "float";
|
|
135
|
+
readonly default: 0;
|
|
136
|
+
readonly min: 0;
|
|
137
|
+
readonly max: 20;
|
|
138
|
+
readonly step: 0.05;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
export declare function executeSolver(context: NodeExecutionContext<typeof solverDefinition>): void;
|
|
143
|
+
declare const noiseForceDefinition: {
|
|
144
|
+
readonly apiVersion: 1;
|
|
145
|
+
readonly label: "POP Noise Force";
|
|
146
|
+
readonly description: "Push particles along a heading read from a noise field.";
|
|
147
|
+
readonly icon: "Wind";
|
|
148
|
+
readonly runsOn: "portable";
|
|
149
|
+
readonly inputs: {
|
|
150
|
+
readonly particles: {
|
|
151
|
+
readonly kind: "data";
|
|
152
|
+
readonly type: "geometry";
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
readonly outputs: {
|
|
156
|
+
readonly geometry: {
|
|
157
|
+
readonly kind: "data";
|
|
158
|
+
readonly type: "geometry";
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
readonly props: {
|
|
162
|
+
readonly amplitude: {
|
|
163
|
+
readonly type: "float";
|
|
164
|
+
readonly default: 6;
|
|
165
|
+
readonly min: 0;
|
|
166
|
+
readonly max: 500;
|
|
167
|
+
readonly step: 0.5;
|
|
168
|
+
};
|
|
169
|
+
readonly frequency: {
|
|
170
|
+
readonly type: "float";
|
|
171
|
+
readonly default: 0.3;
|
|
172
|
+
readonly min: 0.001;
|
|
173
|
+
readonly max: 10;
|
|
174
|
+
readonly step: 0.01;
|
|
175
|
+
};
|
|
176
|
+
readonly evolve: {
|
|
177
|
+
readonly type: "float";
|
|
178
|
+
readonly default: 0;
|
|
179
|
+
readonly min: -1000;
|
|
180
|
+
readonly max: 1000;
|
|
181
|
+
readonly step: 0.01;
|
|
182
|
+
};
|
|
183
|
+
readonly timestep: {
|
|
184
|
+
readonly type: "float";
|
|
185
|
+
readonly default: number;
|
|
186
|
+
readonly min: number;
|
|
187
|
+
readonly max: 1;
|
|
188
|
+
readonly step: number;
|
|
189
|
+
};
|
|
190
|
+
readonly seed: {
|
|
191
|
+
readonly type: "int";
|
|
192
|
+
readonly default: 7;
|
|
193
|
+
readonly min: 0;
|
|
194
|
+
readonly max: 999999;
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Accumulates into `force` and moves nothing — Houdini's POP model.
|
|
200
|
+
*
|
|
201
|
+
* Changed 2026-09-09, and it is a correctness fix rather than a refinement.
|
|
202
|
+
* This node used to call `step()`, which integrates. Chain two force nodes
|
|
203
|
+
* inside a `cascade.core.Feedback` and time then advances **twice per step**:
|
|
204
|
+
* the particles travel further than the timestep says, reordering nodes that
|
|
205
|
+
* ought to commute changes the result, and nothing reports anything. That is
|
|
206
|
+
* unreadable from a picture, so the model has to be right rather than tested.
|
|
207
|
+
*/
|
|
208
|
+
export declare function executeNoiseForce(context: NodeExecutionContext<typeof noiseForceDefinition>): void;
|
|
209
|
+
declare const separateDefinition: {
|
|
210
|
+
readonly apiVersion: 1;
|
|
211
|
+
readonly label: "POP Separate";
|
|
212
|
+
readonly description: "Push neighbours apart within a radius.";
|
|
213
|
+
readonly icon: "Move3d";
|
|
214
|
+
readonly runsOn: "portable";
|
|
215
|
+
readonly inputs: {
|
|
216
|
+
readonly particles: {
|
|
217
|
+
readonly kind: "data";
|
|
218
|
+
readonly type: "geometry";
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
readonly outputs: {
|
|
222
|
+
readonly geometry: {
|
|
223
|
+
readonly kind: "data";
|
|
224
|
+
readonly type: "geometry";
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
readonly props: {
|
|
228
|
+
readonly radius: {
|
|
229
|
+
readonly type: "float";
|
|
230
|
+
readonly default: 0.5;
|
|
231
|
+
readonly min: 0;
|
|
232
|
+
readonly max: 100;
|
|
233
|
+
readonly step: 0.01;
|
|
234
|
+
};
|
|
235
|
+
readonly strength: {
|
|
236
|
+
readonly type: "float";
|
|
237
|
+
readonly default: 4;
|
|
238
|
+
readonly min: 0;
|
|
239
|
+
readonly max: 500;
|
|
240
|
+
readonly step: 0.1;
|
|
241
|
+
};
|
|
242
|
+
readonly timestep: {
|
|
243
|
+
readonly type: "float";
|
|
244
|
+
readonly default: number;
|
|
245
|
+
readonly min: number;
|
|
246
|
+
readonly max: 1;
|
|
247
|
+
readonly step: number;
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
export declare function executeSeparate(context: NodeExecutionContext<typeof separateDefinition>): void;
|
|
252
|
+
/**
|
|
253
|
+
* `cascade.pop.FieldForce` — the flow field, as a node.
|
|
254
|
+
*
|
|
255
|
+
* Extracted from `pop.Simulate`'s `field_*` props on 2026-09-09 so that the
|
|
256
|
+
* force which actually makes the calligraphy is a node you can see inside the
|
|
257
|
+
* container, reorder, and drive from a ramp. Two components, and they are the
|
|
258
|
+
* whole effect: `normal` pulls along the field's `N` toward the spine of the
|
|
259
|
+
* type, `tangential` pushes perpendicular to it, around the outline. The
|
|
260
|
+
* balance between them is what turns a hairball into a stroke.
|
|
261
|
+
*
|
|
262
|
+
* `level` and `hold` are the reason the lines follow the letters rather than
|
|
263
|
+
* collapsing onto them: instead of climbing to the field's peak, a particle
|
|
264
|
+
* holds a chosen contour. Marcus's own note on wanting this: *"we want the
|
|
265
|
+
* lines to extend and really flow around the letters, creating an elegant
|
|
266
|
+
* calligraphy… not hairballs or knots."*
|
|
267
|
+
*/
|
|
268
|
+
declare const fieldForceDefinition: {
|
|
269
|
+
readonly apiVersion: 1;
|
|
270
|
+
readonly label: "POP Field Force";
|
|
271
|
+
readonly description: "Pull particles along a field's gradient and around its contours. The calligraphy force.";
|
|
272
|
+
readonly icon: "Waves";
|
|
273
|
+
readonly runsOn: "portable";
|
|
274
|
+
readonly inputs: {
|
|
275
|
+
readonly particles: {
|
|
276
|
+
readonly kind: "data";
|
|
277
|
+
readonly type: "geometry";
|
|
278
|
+
};
|
|
279
|
+
/** The field: points carrying `N` (the gradient) and optionally `level`. */
|
|
280
|
+
readonly field: {
|
|
281
|
+
readonly kind: "data";
|
|
282
|
+
readonly type: "geometry";
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
readonly outputs: {
|
|
286
|
+
readonly geometry: {
|
|
287
|
+
readonly kind: "data";
|
|
288
|
+
readonly type: "geometry";
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
readonly props: {
|
|
292
|
+
readonly normal: {
|
|
293
|
+
readonly type: "float";
|
|
294
|
+
readonly default: 0;
|
|
295
|
+
readonly min: -100000;
|
|
296
|
+
readonly max: 100000;
|
|
297
|
+
readonly label: "Normal";
|
|
298
|
+
readonly description: "Along N, toward the spine. Negative pushes away.";
|
|
299
|
+
};
|
|
300
|
+
readonly tangential: {
|
|
301
|
+
readonly type: "float";
|
|
302
|
+
readonly default: 2600;
|
|
303
|
+
readonly min: -100000;
|
|
304
|
+
readonly max: 100000;
|
|
305
|
+
readonly label: "Tangential";
|
|
306
|
+
readonly description: "Perpendicular to N, around the outline. Sign picks the direction.";
|
|
307
|
+
};
|
|
308
|
+
readonly radius: {
|
|
309
|
+
readonly type: "float";
|
|
310
|
+
readonly default: 20;
|
|
311
|
+
readonly min: 0;
|
|
312
|
+
readonly max: 10000;
|
|
313
|
+
readonly label: "Radius";
|
|
314
|
+
readonly description: "How far a field sample reaches.";
|
|
315
|
+
};
|
|
316
|
+
readonly level: {
|
|
317
|
+
readonly type: "float";
|
|
318
|
+
readonly default: 0.28;
|
|
319
|
+
readonly min: 0;
|
|
320
|
+
readonly max: 1;
|
|
321
|
+
readonly step: 0.01;
|
|
322
|
+
readonly label: "Level";
|
|
323
|
+
readonly description: "The contour to hold, rather than climbing to the peak.";
|
|
324
|
+
};
|
|
325
|
+
readonly hold: {
|
|
326
|
+
readonly type: "float";
|
|
327
|
+
readonly default: 5200;
|
|
328
|
+
readonly min: 0;
|
|
329
|
+
readonly max: 100000;
|
|
330
|
+
readonly label: "Hold";
|
|
331
|
+
readonly description: "How hard to hold that contour.";
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
export declare function executeFieldForce(context: NodeExecutionContext<typeof fieldForceDefinition>): void;
|
|
336
|
+
/**
|
|
337
|
+
* `cascade.pop.Trail` — trails from a Feedback container's history.
|
|
338
|
+
*
|
|
339
|
+
* The node that makes the network version of the piece possible at all. A trail
|
|
340
|
+
* needs the *frames*, and a node downstream of a simulation sees one — which
|
|
341
|
+
* draws nothing. `cascade.core.Feedback` keeps a bounded history for exactly
|
|
342
|
+
* this, and this node consumes it.
|
|
343
|
+
*
|
|
344
|
+
* The parameter names are Houdini's Trail SOP: `result`, `length`,
|
|
345
|
+
* `increment`, `velocityscale`.
|
|
346
|
+
*/
|
|
347
|
+
declare const trailDefinition: {
|
|
348
|
+
readonly apiVersion: 1;
|
|
349
|
+
readonly label: "POP Trail";
|
|
350
|
+
readonly description: "Build trails from a Feedback container's history.";
|
|
351
|
+
readonly icon: "Activity";
|
|
352
|
+
readonly runsOn: "portable";
|
|
353
|
+
readonly inputs: {
|
|
354
|
+
/** The container's `history` output: every kept step, oldest first. */
|
|
355
|
+
readonly history: {
|
|
356
|
+
readonly kind: "data";
|
|
357
|
+
readonly type: "array";
|
|
358
|
+
};
|
|
359
|
+
/** The current state, for `preserve` and for the newest point of each trail. */
|
|
360
|
+
readonly particles: {
|
|
361
|
+
readonly kind: "data";
|
|
362
|
+
readonly type: "geometry";
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
readonly outputs: {
|
|
366
|
+
readonly geometry: {
|
|
367
|
+
readonly kind: "data";
|
|
368
|
+
readonly type: "geometry";
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
readonly props: {
|
|
372
|
+
readonly result: {
|
|
373
|
+
readonly type: "string";
|
|
374
|
+
readonly default: "polylines";
|
|
375
|
+
readonly control: "select";
|
|
376
|
+
readonly options: readonly [{
|
|
377
|
+
readonly value: "polylines";
|
|
378
|
+
readonly label: "Polylines - one line per particle";
|
|
379
|
+
}, {
|
|
380
|
+
readonly value: "preserve";
|
|
381
|
+
readonly label: "Preserve - pass the points through";
|
|
382
|
+
}];
|
|
383
|
+
readonly label: "Result";
|
|
384
|
+
};
|
|
385
|
+
readonly length: {
|
|
386
|
+
readonly type: "int";
|
|
387
|
+
readonly default: 12;
|
|
388
|
+
readonly min: 1;
|
|
389
|
+
readonly max: 100000;
|
|
390
|
+
readonly label: "Length";
|
|
391
|
+
};
|
|
392
|
+
readonly increment: {
|
|
393
|
+
readonly type: "int";
|
|
394
|
+
readonly default: 1;
|
|
395
|
+
readonly min: 1;
|
|
396
|
+
readonly max: 1000;
|
|
397
|
+
readonly label: "Increment";
|
|
398
|
+
};
|
|
399
|
+
readonly velocityscale: {
|
|
400
|
+
readonly type: "float";
|
|
401
|
+
readonly default: 1;
|
|
402
|
+
readonly min: 0;
|
|
403
|
+
readonly max: 100;
|
|
404
|
+
readonly step: 0.01;
|
|
405
|
+
readonly label: "Velocity Scale";
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
export declare function executeTrail(context: NodeExecutionContext<typeof trailDefinition>): void;
|
|
410
|
+
declare const simulateDefinition: {
|
|
411
|
+
readonly apiVersion: 1;
|
|
412
|
+
readonly label: "POP Simulate";
|
|
413
|
+
readonly description: "Re-simulate a particle system from frame zero to this frame.";
|
|
414
|
+
readonly icon: "Rewind";
|
|
415
|
+
readonly runsOn: "portable";
|
|
416
|
+
readonly inputs: {
|
|
417
|
+
/** Where particles are born. Its points are the birth positions. */
|
|
418
|
+
readonly geometry: {
|
|
419
|
+
readonly kind: "data";
|
|
420
|
+
readonly type: "geometry";
|
|
421
|
+
};
|
|
422
|
+
/** The frame to simulate up to. Drive it with `$F`. An input rather than
|
|
423
|
+
* a prop because the whole node is a function of it, and because a node
|
|
424
|
+
* that read the clock itself would be the thing `AGENTS.md` forbids. */
|
|
425
|
+
readonly frame: {
|
|
426
|
+
readonly kind: "data";
|
|
427
|
+
readonly type: "float";
|
|
428
|
+
readonly default: 1;
|
|
429
|
+
};
|
|
430
|
+
/**
|
|
431
|
+
* Points particles are drawn toward. Houdini's POP Attract.
|
|
432
|
+
*
|
|
433
|
+
* **Geometry rather than an image, and that was a correction.** The
|
|
434
|
+
* original piece renders its word to a canvas, blurs it, reads the pixels
|
|
435
|
+
* back and takes central differences — so its target really is a raster
|
|
436
|
+
* field, and an `image` input was the first thing tried here. It was wrong
|
|
437
|
+
* for this node: decoding an image needs a capability, the media
|
|
438
|
+
* capability hands back a host-specific lease rather than a portable
|
|
439
|
+
* raster, and a POP node doing IO stops being the pure arithmetic that
|
|
440
|
+
* makes it cook identically in both hosts.
|
|
441
|
+
*
|
|
442
|
+
* So the rasterising belongs upstream, in whatever node knows about text
|
|
443
|
+
* or photographs, and what reaches the solver is a set of target points.
|
|
444
|
+
* `fieldForce` in `pop/forces.ts` still takes a sampled raster for a node
|
|
445
|
+
* that has one; this input is the composable half.
|
|
446
|
+
*/
|
|
447
|
+
readonly attract: {
|
|
448
|
+
readonly kind: "data";
|
|
449
|
+
readonly type: "geometry";
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* A flow field, as points carrying `N`.
|
|
453
|
+
*
|
|
454
|
+
* Marcus's description of the original: *"a delicate balance between a
|
|
455
|
+
* force pulling the particles towards the centre spine of the type and
|
|
456
|
+
* tangentially around their outlines."* This is that field — the gradient
|
|
457
|
+
* of a BLURRED letterform, whose ridge runs down each stroke's middle, so
|
|
458
|
+
* the gradient points at the spine and its perpendicular runs along the
|
|
459
|
+
* outline. `field_normal` and `field_tangential` are the two halves of the
|
|
460
|
+
* balance.
|
|
461
|
+
*/
|
|
462
|
+
readonly field: {
|
|
463
|
+
readonly kind: "data";
|
|
464
|
+
readonly type: "geometry";
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
readonly outputs: {
|
|
468
|
+
/** The particles, one point each. */
|
|
469
|
+
readonly geometry: {
|
|
470
|
+
readonly kind: "data";
|
|
471
|
+
readonly type: "geometry";
|
|
472
|
+
};
|
|
473
|
+
/**
|
|
474
|
+
* The trails, one open polyline per particle.
|
|
475
|
+
*
|
|
476
|
+
* Emitted here rather than by a standalone `Trail` node, and that is a
|
|
477
|
+
* consequence of re-simulation rather than a shortcut. Houdini's Trail SOP
|
|
478
|
+
* reads a rolling window the cook already holds, because Houdini's cook is
|
|
479
|
+
* stateful; Cascade's node is a pure function of its inputs, so a
|
|
480
|
+
* standalone `Trail` can only see the frame on its port — one frame, which
|
|
481
|
+
* draws nothing. This node replays every frame from zero, so it has the
|
|
482
|
+
* whole history for free, and a node that draws nothing would be worse than
|
|
483
|
+
* no node.
|
|
484
|
+
*
|
|
485
|
+
* When `CookScheduler` owns the rolling window the plan describes,
|
|
486
|
+
* `cascade.pop.Trail` becomes a real node and this output stays as the
|
|
487
|
+
* cheap path. The parameters are Houdini's either way.
|
|
488
|
+
*/
|
|
489
|
+
readonly trails: {
|
|
490
|
+
readonly kind: "data";
|
|
491
|
+
readonly type: "geometry";
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
readonly props: {
|
|
495
|
+
/** Frames of history in the trails. Houdini's Trail `length`. */
|
|
496
|
+
readonly trail_length: {
|
|
497
|
+
readonly type: "int";
|
|
498
|
+
readonly default: 14;
|
|
499
|
+
readonly min: 1;
|
|
500
|
+
readonly max: 500;
|
|
501
|
+
};
|
|
502
|
+
/** Use every nth frame. Houdini's `increment`. */
|
|
503
|
+
readonly trail_increment: {
|
|
504
|
+
readonly type: "int";
|
|
505
|
+
readonly default: 1;
|
|
506
|
+
readonly min: 1;
|
|
507
|
+
readonly max: 32;
|
|
508
|
+
};
|
|
509
|
+
/**
|
|
510
|
+
* Re-simulation, and why this is the first implementation rather than a
|
|
511
|
+
* placeholder for a cached one.
|
|
512
|
+
*
|
|
513
|
+
* `PLAN particles.md`: re-simulation from the start frame is the
|
|
514
|
+
* *definition* of correctness, and a cache is an accelerator holding
|
|
515
|
+
* nothing re-simulation would not have produced. So this node is the
|
|
516
|
+
* reference: it reads a frame, replays every step from zero, and depends on
|
|
517
|
+
* no state whatsoever. Scrubbing is O(frame) and that is the honest cost —
|
|
518
|
+
* a checkpoint cache makes it fast later without changing a single output,
|
|
519
|
+
* which is exactly the property a cache should have.
|
|
520
|
+
*/
|
|
521
|
+
readonly substeps: {
|
|
522
|
+
readonly type: "int";
|
|
523
|
+
readonly default: 1;
|
|
524
|
+
readonly min: 1;
|
|
525
|
+
readonly max: 32;
|
|
526
|
+
};
|
|
527
|
+
readonly timestep: {
|
|
528
|
+
readonly type: "float";
|
|
529
|
+
readonly default: number;
|
|
530
|
+
readonly min: number;
|
|
531
|
+
readonly max: 1;
|
|
532
|
+
readonly step: number;
|
|
533
|
+
};
|
|
534
|
+
/** Per frame, as on Source. */
|
|
535
|
+
readonly impulse: {
|
|
536
|
+
readonly type: "int";
|
|
537
|
+
readonly default: 6;
|
|
538
|
+
readonly min: 0;
|
|
539
|
+
readonly max: 100000;
|
|
540
|
+
};
|
|
541
|
+
readonly life: {
|
|
542
|
+
readonly type: "float";
|
|
543
|
+
readonly default: 4;
|
|
544
|
+
readonly min: 0;
|
|
545
|
+
readonly max: 3600;
|
|
546
|
+
readonly step: 0.1;
|
|
547
|
+
};
|
|
548
|
+
readonly lifevar: {
|
|
549
|
+
readonly type: "float";
|
|
550
|
+
readonly default: 0.4;
|
|
551
|
+
readonly min: 0;
|
|
552
|
+
readonly max: 1;
|
|
553
|
+
readonly step: 0.01;
|
|
554
|
+
};
|
|
555
|
+
readonly force: {
|
|
556
|
+
readonly type: "vec2";
|
|
557
|
+
readonly default: readonly [0, 0];
|
|
558
|
+
};
|
|
559
|
+
readonly airresist: {
|
|
560
|
+
readonly type: "float";
|
|
561
|
+
readonly default: 0.4;
|
|
562
|
+
readonly min: 0;
|
|
563
|
+
readonly max: 20;
|
|
564
|
+
readonly step: 0.05;
|
|
565
|
+
};
|
|
566
|
+
readonly noise_amplitude: {
|
|
567
|
+
readonly type: "float";
|
|
568
|
+
readonly default: 14;
|
|
569
|
+
readonly min: 0;
|
|
570
|
+
readonly max: 500;
|
|
571
|
+
readonly step: 0.5;
|
|
572
|
+
};
|
|
573
|
+
readonly noise_frequency: {
|
|
574
|
+
readonly type: "float";
|
|
575
|
+
readonly default: 0.02;
|
|
576
|
+
readonly min: 0.001;
|
|
577
|
+
readonly max: 10;
|
|
578
|
+
readonly step: 0.001;
|
|
579
|
+
};
|
|
580
|
+
/** Turns per second of field evolution. */
|
|
581
|
+
readonly noise_evolve: {
|
|
582
|
+
readonly type: "float";
|
|
583
|
+
readonly default: 0.08;
|
|
584
|
+
readonly min: -10;
|
|
585
|
+
readonly max: 10;
|
|
586
|
+
readonly step: 0.01;
|
|
587
|
+
};
|
|
588
|
+
/** Toward the spine, up the field's gradient. */
|
|
589
|
+
readonly field_normal: {
|
|
590
|
+
readonly type: "float";
|
|
591
|
+
readonly default: 0;
|
|
592
|
+
readonly min: -20000;
|
|
593
|
+
readonly max: 20000;
|
|
594
|
+
readonly step: 1;
|
|
595
|
+
};
|
|
596
|
+
/** Around the outline, perpendicular to the gradient. Sign picks which way. */
|
|
597
|
+
readonly field_tangential: {
|
|
598
|
+
readonly type: "float";
|
|
599
|
+
readonly default: 0;
|
|
600
|
+
readonly min: -20000;
|
|
601
|
+
readonly max: 20000;
|
|
602
|
+
readonly step: 1;
|
|
603
|
+
};
|
|
604
|
+
/** Field samples beyond this are ignored, and it is the grid's cell size. */
|
|
605
|
+
readonly field_radius: {
|
|
606
|
+
readonly type: "float";
|
|
607
|
+
readonly default: 24;
|
|
608
|
+
readonly min: 0.5;
|
|
609
|
+
readonly max: 2000;
|
|
610
|
+
readonly step: 0.5;
|
|
611
|
+
};
|
|
612
|
+
/**
|
|
613
|
+
* The contour to hold, as a field level 0..1, with `field_hold` as how
|
|
614
|
+
* firmly.
|
|
615
|
+
*
|
|
616
|
+
* Marcus: *"the lines flow tangentially around the letters in a bundle …
|
|
617
|
+
* loosely describing the type."* A bundle is a contour, so holding a level
|
|
618
|
+
* and letting the tangent carry particles round it is the mechanism —
|
|
619
|
+
* pulling them to the spine instead collapses each letter to a point, and
|
|
620
|
+
* pure tangential flow lets every particle converge onto one level set.
|
|
621
|
+
*/
|
|
622
|
+
readonly field_level: {
|
|
623
|
+
readonly type: "float";
|
|
624
|
+
readonly default: 0.35;
|
|
625
|
+
readonly min: 0;
|
|
626
|
+
readonly max: 1;
|
|
627
|
+
readonly step: 0.01;
|
|
628
|
+
};
|
|
629
|
+
readonly field_hold: {
|
|
630
|
+
readonly type: "float";
|
|
631
|
+
readonly default: 0;
|
|
632
|
+
readonly min: 0;
|
|
633
|
+
readonly max: 40000;
|
|
634
|
+
readonly step: 10;
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* Scatter births across this box instead of on the source geometry's
|
|
638
|
+
* points, `[minX, minY, maxX, maxY]`. All zero uses the points.
|
|
639
|
+
*
|
|
640
|
+
* Houdini's POP Source has the same choice — *Scatter onto Surface* against
|
|
641
|
+
* *Points* — and Marcus's note is that the original spawns *around* the
|
|
642
|
+
* type rather than on it: *"The particles should spawn in the area around
|
|
643
|
+
* the type … and then the forces should drag them around."* Birthing on
|
|
644
|
+
* the letterforms puts every particle where it is already going, so
|
|
645
|
+
* nothing travels and the field has nothing to reveal.
|
|
646
|
+
*/
|
|
647
|
+
readonly birth_area: {
|
|
648
|
+
readonly type: "vec4";
|
|
649
|
+
readonly default: readonly [0, 0, 0, 0];
|
|
650
|
+
};
|
|
651
|
+
/**
|
|
652
|
+
* A colour per particle, drawn at birth and fixed for its life.
|
|
653
|
+
*
|
|
654
|
+
* Hashed on the id, so the same particle gets the same colour however many
|
|
655
|
+
* times the frame is re-simulated — a colour drawn from a stream would
|
|
656
|
+
* change on every scrub, which reads as the render being unstable.
|
|
657
|
+
*
|
|
658
|
+
* `[from, to]` in turns around the wheel. Equal values give one hue; a
|
|
659
|
+
* narrow span is usually what a piece wants, because a full-spectrum
|
|
660
|
+
* random set reads as a test pattern rather than as a palette.
|
|
661
|
+
*/
|
|
662
|
+
readonly birth_hue: {
|
|
663
|
+
readonly type: "vec2";
|
|
664
|
+
readonly default: readonly [0, 0];
|
|
665
|
+
readonly min: 0;
|
|
666
|
+
readonly max: 1;
|
|
667
|
+
readonly step: 0.01;
|
|
668
|
+
};
|
|
669
|
+
readonly birth_saturation: {
|
|
670
|
+
readonly type: "float";
|
|
671
|
+
readonly default: 0.6;
|
|
672
|
+
readonly min: 0;
|
|
673
|
+
readonly max: 1;
|
|
674
|
+
readonly step: 0.01;
|
|
675
|
+
};
|
|
676
|
+
readonly birth_value: {
|
|
677
|
+
readonly type: "float";
|
|
678
|
+
readonly default: 1;
|
|
679
|
+
readonly min: 0;
|
|
680
|
+
readonly max: 1;
|
|
681
|
+
readonly step: 0.01;
|
|
682
|
+
};
|
|
683
|
+
/** How hard the targets pull. Houdini's POP Attract strength. */
|
|
684
|
+
readonly attract_amplitude: {
|
|
685
|
+
readonly type: "float";
|
|
686
|
+
readonly default: 0;
|
|
687
|
+
readonly min: 0;
|
|
688
|
+
readonly max: 20000;
|
|
689
|
+
readonly step: 1;
|
|
690
|
+
};
|
|
691
|
+
/** Beyond this distance a target is ignored, which is what keeps the pull
|
|
692
|
+
* local and the cost bounded. 0 means every target pulls every particle. */
|
|
693
|
+
readonly attract_radius: {
|
|
694
|
+
readonly type: "float";
|
|
695
|
+
readonly default: 60;
|
|
696
|
+
readonly min: 0;
|
|
697
|
+
readonly max: 5000;
|
|
698
|
+
readonly step: 1;
|
|
699
|
+
};
|
|
700
|
+
readonly separate_radius: {
|
|
701
|
+
readonly type: "float";
|
|
702
|
+
readonly default: 0;
|
|
703
|
+
readonly min: 0;
|
|
704
|
+
readonly max: 500;
|
|
705
|
+
readonly step: 0.5;
|
|
706
|
+
};
|
|
707
|
+
readonly separate_strength: {
|
|
708
|
+
readonly type: "float";
|
|
709
|
+
readonly default: 12;
|
|
710
|
+
readonly min: 0;
|
|
711
|
+
readonly max: 500;
|
|
712
|
+
readonly step: 0.5;
|
|
713
|
+
};
|
|
714
|
+
readonly maxspeed: {
|
|
715
|
+
readonly type: "float";
|
|
716
|
+
readonly default: 60;
|
|
717
|
+
readonly min: 0;
|
|
718
|
+
readonly max: 10000;
|
|
719
|
+
readonly step: 1;
|
|
720
|
+
};
|
|
721
|
+
/** `[minX, minY, maxX, maxY]`, or all zero for open space. The original
|
|
722
|
+
* piece is toroidal. */
|
|
723
|
+
readonly wrap: {
|
|
724
|
+
readonly type: "vec4";
|
|
725
|
+
readonly default: readonly [0, 0, 0, 0];
|
|
726
|
+
};
|
|
727
|
+
readonly seed: {
|
|
728
|
+
readonly type: "int";
|
|
729
|
+
readonly default: 7;
|
|
730
|
+
readonly min: 0;
|
|
731
|
+
readonly max: 999999;
|
|
732
|
+
};
|
|
733
|
+
};
|
|
734
|
+
};
|
|
735
|
+
export declare function executeSimulate(context: NodeExecutionContext<typeof simulateDefinition>): void;
|
|
736
|
+
export declare const simulateRegistration: {
|
|
737
|
+
kind: "definition-v1";
|
|
738
|
+
moduleId: string;
|
|
739
|
+
definition: {
|
|
740
|
+
readonly apiVersion: 1;
|
|
741
|
+
readonly label: "POP Simulate";
|
|
742
|
+
readonly description: "Re-simulate a particle system from frame zero to this frame.";
|
|
743
|
+
readonly icon: "Rewind";
|
|
744
|
+
readonly runsOn: "portable";
|
|
745
|
+
readonly inputs: {
|
|
746
|
+
/** Where particles are born. Its points are the birth positions. */
|
|
747
|
+
readonly geometry: {
|
|
748
|
+
readonly kind: "data";
|
|
749
|
+
readonly type: "geometry";
|
|
750
|
+
};
|
|
751
|
+
/** The frame to simulate up to. Drive it with `$F`. An input rather than
|
|
752
|
+
* a prop because the whole node is a function of it, and because a node
|
|
753
|
+
* that read the clock itself would be the thing `AGENTS.md` forbids. */
|
|
754
|
+
readonly frame: {
|
|
755
|
+
readonly kind: "data";
|
|
756
|
+
readonly type: "float";
|
|
757
|
+
readonly default: 1;
|
|
758
|
+
};
|
|
759
|
+
/**
|
|
760
|
+
* Points particles are drawn toward. Houdini's POP Attract.
|
|
761
|
+
*
|
|
762
|
+
* **Geometry rather than an image, and that was a correction.** The
|
|
763
|
+
* original piece renders its word to a canvas, blurs it, reads the pixels
|
|
764
|
+
* back and takes central differences — so its target really is a raster
|
|
765
|
+
* field, and an `image` input was the first thing tried here. It was wrong
|
|
766
|
+
* for this node: decoding an image needs a capability, the media
|
|
767
|
+
* capability hands back a host-specific lease rather than a portable
|
|
768
|
+
* raster, and a POP node doing IO stops being the pure arithmetic that
|
|
769
|
+
* makes it cook identically in both hosts.
|
|
770
|
+
*
|
|
771
|
+
* So the rasterising belongs upstream, in whatever node knows about text
|
|
772
|
+
* or photographs, and what reaches the solver is a set of target points.
|
|
773
|
+
* `fieldForce` in `pop/forces.ts` still takes a sampled raster for a node
|
|
774
|
+
* that has one; this input is the composable half.
|
|
775
|
+
*/
|
|
776
|
+
readonly attract: {
|
|
777
|
+
readonly kind: "data";
|
|
778
|
+
readonly type: "geometry";
|
|
779
|
+
};
|
|
780
|
+
/**
|
|
781
|
+
* A flow field, as points carrying `N`.
|
|
782
|
+
*
|
|
783
|
+
* Marcus's description of the original: *"a delicate balance between a
|
|
784
|
+
* force pulling the particles towards the centre spine of the type and
|
|
785
|
+
* tangentially around their outlines."* This is that field — the gradient
|
|
786
|
+
* of a BLURRED letterform, whose ridge runs down each stroke's middle, so
|
|
787
|
+
* the gradient points at the spine and its perpendicular runs along the
|
|
788
|
+
* outline. `field_normal` and `field_tangential` are the two halves of the
|
|
789
|
+
* balance.
|
|
790
|
+
*/
|
|
791
|
+
readonly field: {
|
|
792
|
+
readonly kind: "data";
|
|
793
|
+
readonly type: "geometry";
|
|
794
|
+
};
|
|
795
|
+
};
|
|
796
|
+
readonly outputs: {
|
|
797
|
+
/** The particles, one point each. */
|
|
798
|
+
readonly geometry: {
|
|
799
|
+
readonly kind: "data";
|
|
800
|
+
readonly type: "geometry";
|
|
801
|
+
};
|
|
802
|
+
/**
|
|
803
|
+
* The trails, one open polyline per particle.
|
|
804
|
+
*
|
|
805
|
+
* Emitted here rather than by a standalone `Trail` node, and that is a
|
|
806
|
+
* consequence of re-simulation rather than a shortcut. Houdini's Trail SOP
|
|
807
|
+
* reads a rolling window the cook already holds, because Houdini's cook is
|
|
808
|
+
* stateful; Cascade's node is a pure function of its inputs, so a
|
|
809
|
+
* standalone `Trail` can only see the frame on its port — one frame, which
|
|
810
|
+
* draws nothing. This node replays every frame from zero, so it has the
|
|
811
|
+
* whole history for free, and a node that draws nothing would be worse than
|
|
812
|
+
* no node.
|
|
813
|
+
*
|
|
814
|
+
* When `CookScheduler` owns the rolling window the plan describes,
|
|
815
|
+
* `cascade.pop.Trail` becomes a real node and this output stays as the
|
|
816
|
+
* cheap path. The parameters are Houdini's either way.
|
|
817
|
+
*/
|
|
818
|
+
readonly trails: {
|
|
819
|
+
readonly kind: "data";
|
|
820
|
+
readonly type: "geometry";
|
|
821
|
+
};
|
|
822
|
+
};
|
|
823
|
+
readonly props: {
|
|
824
|
+
/** Frames of history in the trails. Houdini's Trail `length`. */
|
|
825
|
+
readonly trail_length: {
|
|
826
|
+
readonly type: "int";
|
|
827
|
+
readonly default: 14;
|
|
828
|
+
readonly min: 1;
|
|
829
|
+
readonly max: 500;
|
|
830
|
+
};
|
|
831
|
+
/** Use every nth frame. Houdini's `increment`. */
|
|
832
|
+
readonly trail_increment: {
|
|
833
|
+
readonly type: "int";
|
|
834
|
+
readonly default: 1;
|
|
835
|
+
readonly min: 1;
|
|
836
|
+
readonly max: 32;
|
|
837
|
+
};
|
|
838
|
+
/**
|
|
839
|
+
* Re-simulation, and why this is the first implementation rather than a
|
|
840
|
+
* placeholder for a cached one.
|
|
841
|
+
*
|
|
842
|
+
* `PLAN particles.md`: re-simulation from the start frame is the
|
|
843
|
+
* *definition* of correctness, and a cache is an accelerator holding
|
|
844
|
+
* nothing re-simulation would not have produced. So this node is the
|
|
845
|
+
* reference: it reads a frame, replays every step from zero, and depends on
|
|
846
|
+
* no state whatsoever. Scrubbing is O(frame) and that is the honest cost —
|
|
847
|
+
* a checkpoint cache makes it fast later without changing a single output,
|
|
848
|
+
* which is exactly the property a cache should have.
|
|
849
|
+
*/
|
|
850
|
+
readonly substeps: {
|
|
851
|
+
readonly type: "int";
|
|
852
|
+
readonly default: 1;
|
|
853
|
+
readonly min: 1;
|
|
854
|
+
readonly max: 32;
|
|
855
|
+
};
|
|
856
|
+
readonly timestep: {
|
|
857
|
+
readonly type: "float";
|
|
858
|
+
readonly default: number;
|
|
859
|
+
readonly min: number;
|
|
860
|
+
readonly max: 1;
|
|
861
|
+
readonly step: number;
|
|
862
|
+
};
|
|
863
|
+
/** Per frame, as on Source. */
|
|
864
|
+
readonly impulse: {
|
|
865
|
+
readonly type: "int";
|
|
866
|
+
readonly default: 6;
|
|
867
|
+
readonly min: 0;
|
|
868
|
+
readonly max: 100000;
|
|
869
|
+
};
|
|
870
|
+
readonly life: {
|
|
871
|
+
readonly type: "float";
|
|
872
|
+
readonly default: 4;
|
|
873
|
+
readonly min: 0;
|
|
874
|
+
readonly max: 3600;
|
|
875
|
+
readonly step: 0.1;
|
|
876
|
+
};
|
|
877
|
+
readonly lifevar: {
|
|
878
|
+
readonly type: "float";
|
|
879
|
+
readonly default: 0.4;
|
|
880
|
+
readonly min: 0;
|
|
881
|
+
readonly max: 1;
|
|
882
|
+
readonly step: 0.01;
|
|
883
|
+
};
|
|
884
|
+
readonly force: {
|
|
885
|
+
readonly type: "vec2";
|
|
886
|
+
readonly default: readonly [0, 0];
|
|
887
|
+
};
|
|
888
|
+
readonly airresist: {
|
|
889
|
+
readonly type: "float";
|
|
890
|
+
readonly default: 0.4;
|
|
891
|
+
readonly min: 0;
|
|
892
|
+
readonly max: 20;
|
|
893
|
+
readonly step: 0.05;
|
|
894
|
+
};
|
|
895
|
+
readonly noise_amplitude: {
|
|
896
|
+
readonly type: "float";
|
|
897
|
+
readonly default: 14;
|
|
898
|
+
readonly min: 0;
|
|
899
|
+
readonly max: 500;
|
|
900
|
+
readonly step: 0.5;
|
|
901
|
+
};
|
|
902
|
+
readonly noise_frequency: {
|
|
903
|
+
readonly type: "float";
|
|
904
|
+
readonly default: 0.02;
|
|
905
|
+
readonly min: 0.001;
|
|
906
|
+
readonly max: 10;
|
|
907
|
+
readonly step: 0.001;
|
|
908
|
+
};
|
|
909
|
+
/** Turns per second of field evolution. */
|
|
910
|
+
readonly noise_evolve: {
|
|
911
|
+
readonly type: "float";
|
|
912
|
+
readonly default: 0.08;
|
|
913
|
+
readonly min: -10;
|
|
914
|
+
readonly max: 10;
|
|
915
|
+
readonly step: 0.01;
|
|
916
|
+
};
|
|
917
|
+
/** Toward the spine, up the field's gradient. */
|
|
918
|
+
readonly field_normal: {
|
|
919
|
+
readonly type: "float";
|
|
920
|
+
readonly default: 0;
|
|
921
|
+
readonly min: -20000;
|
|
922
|
+
readonly max: 20000;
|
|
923
|
+
readonly step: 1;
|
|
924
|
+
};
|
|
925
|
+
/** Around the outline, perpendicular to the gradient. Sign picks which way. */
|
|
926
|
+
readonly field_tangential: {
|
|
927
|
+
readonly type: "float";
|
|
928
|
+
readonly default: 0;
|
|
929
|
+
readonly min: -20000;
|
|
930
|
+
readonly max: 20000;
|
|
931
|
+
readonly step: 1;
|
|
932
|
+
};
|
|
933
|
+
/** Field samples beyond this are ignored, and it is the grid's cell size. */
|
|
934
|
+
readonly field_radius: {
|
|
935
|
+
readonly type: "float";
|
|
936
|
+
readonly default: 24;
|
|
937
|
+
readonly min: 0.5;
|
|
938
|
+
readonly max: 2000;
|
|
939
|
+
readonly step: 0.5;
|
|
940
|
+
};
|
|
941
|
+
/**
|
|
942
|
+
* The contour to hold, as a field level 0..1, with `field_hold` as how
|
|
943
|
+
* firmly.
|
|
944
|
+
*
|
|
945
|
+
* Marcus: *"the lines flow tangentially around the letters in a bundle …
|
|
946
|
+
* loosely describing the type."* A bundle is a contour, so holding a level
|
|
947
|
+
* and letting the tangent carry particles round it is the mechanism —
|
|
948
|
+
* pulling them to the spine instead collapses each letter to a point, and
|
|
949
|
+
* pure tangential flow lets every particle converge onto one level set.
|
|
950
|
+
*/
|
|
951
|
+
readonly field_level: {
|
|
952
|
+
readonly type: "float";
|
|
953
|
+
readonly default: 0.35;
|
|
954
|
+
readonly min: 0;
|
|
955
|
+
readonly max: 1;
|
|
956
|
+
readonly step: 0.01;
|
|
957
|
+
};
|
|
958
|
+
readonly field_hold: {
|
|
959
|
+
readonly type: "float";
|
|
960
|
+
readonly default: 0;
|
|
961
|
+
readonly min: 0;
|
|
962
|
+
readonly max: 40000;
|
|
963
|
+
readonly step: 10;
|
|
964
|
+
};
|
|
965
|
+
/**
|
|
966
|
+
* Scatter births across this box instead of on the source geometry's
|
|
967
|
+
* points, `[minX, minY, maxX, maxY]`. All zero uses the points.
|
|
968
|
+
*
|
|
969
|
+
* Houdini's POP Source has the same choice — *Scatter onto Surface* against
|
|
970
|
+
* *Points* — and Marcus's note is that the original spawns *around* the
|
|
971
|
+
* type rather than on it: *"The particles should spawn in the area around
|
|
972
|
+
* the type … and then the forces should drag them around."* Birthing on
|
|
973
|
+
* the letterforms puts every particle where it is already going, so
|
|
974
|
+
* nothing travels and the field has nothing to reveal.
|
|
975
|
+
*/
|
|
976
|
+
readonly birth_area: {
|
|
977
|
+
readonly type: "vec4";
|
|
978
|
+
readonly default: readonly [0, 0, 0, 0];
|
|
979
|
+
};
|
|
980
|
+
/**
|
|
981
|
+
* A colour per particle, drawn at birth and fixed for its life.
|
|
982
|
+
*
|
|
983
|
+
* Hashed on the id, so the same particle gets the same colour however many
|
|
984
|
+
* times the frame is re-simulated — a colour drawn from a stream would
|
|
985
|
+
* change on every scrub, which reads as the render being unstable.
|
|
986
|
+
*
|
|
987
|
+
* `[from, to]` in turns around the wheel. Equal values give one hue; a
|
|
988
|
+
* narrow span is usually what a piece wants, because a full-spectrum
|
|
989
|
+
* random set reads as a test pattern rather than as a palette.
|
|
990
|
+
*/
|
|
991
|
+
readonly birth_hue: {
|
|
992
|
+
readonly type: "vec2";
|
|
993
|
+
readonly default: readonly [0, 0];
|
|
994
|
+
readonly min: 0;
|
|
995
|
+
readonly max: 1;
|
|
996
|
+
readonly step: 0.01;
|
|
997
|
+
};
|
|
998
|
+
readonly birth_saturation: {
|
|
999
|
+
readonly type: "float";
|
|
1000
|
+
readonly default: 0.6;
|
|
1001
|
+
readonly min: 0;
|
|
1002
|
+
readonly max: 1;
|
|
1003
|
+
readonly step: 0.01;
|
|
1004
|
+
};
|
|
1005
|
+
readonly birth_value: {
|
|
1006
|
+
readonly type: "float";
|
|
1007
|
+
readonly default: 1;
|
|
1008
|
+
readonly min: 0;
|
|
1009
|
+
readonly max: 1;
|
|
1010
|
+
readonly step: 0.01;
|
|
1011
|
+
};
|
|
1012
|
+
/** How hard the targets pull. Houdini's POP Attract strength. */
|
|
1013
|
+
readonly attract_amplitude: {
|
|
1014
|
+
readonly type: "float";
|
|
1015
|
+
readonly default: 0;
|
|
1016
|
+
readonly min: 0;
|
|
1017
|
+
readonly max: 20000;
|
|
1018
|
+
readonly step: 1;
|
|
1019
|
+
};
|
|
1020
|
+
/** Beyond this distance a target is ignored, which is what keeps the pull
|
|
1021
|
+
* local and the cost bounded. 0 means every target pulls every particle. */
|
|
1022
|
+
readonly attract_radius: {
|
|
1023
|
+
readonly type: "float";
|
|
1024
|
+
readonly default: 60;
|
|
1025
|
+
readonly min: 0;
|
|
1026
|
+
readonly max: 5000;
|
|
1027
|
+
readonly step: 1;
|
|
1028
|
+
};
|
|
1029
|
+
readonly separate_radius: {
|
|
1030
|
+
readonly type: "float";
|
|
1031
|
+
readonly default: 0;
|
|
1032
|
+
readonly min: 0;
|
|
1033
|
+
readonly max: 500;
|
|
1034
|
+
readonly step: 0.5;
|
|
1035
|
+
};
|
|
1036
|
+
readonly separate_strength: {
|
|
1037
|
+
readonly type: "float";
|
|
1038
|
+
readonly default: 12;
|
|
1039
|
+
readonly min: 0;
|
|
1040
|
+
readonly max: 500;
|
|
1041
|
+
readonly step: 0.5;
|
|
1042
|
+
};
|
|
1043
|
+
readonly maxspeed: {
|
|
1044
|
+
readonly type: "float";
|
|
1045
|
+
readonly default: 60;
|
|
1046
|
+
readonly min: 0;
|
|
1047
|
+
readonly max: 10000;
|
|
1048
|
+
readonly step: 1;
|
|
1049
|
+
};
|
|
1050
|
+
/** `[minX, minY, maxX, maxY]`, or all zero for open space. The original
|
|
1051
|
+
* piece is toroidal. */
|
|
1052
|
+
readonly wrap: {
|
|
1053
|
+
readonly type: "vec4";
|
|
1054
|
+
readonly default: readonly [0, 0, 0, 0];
|
|
1055
|
+
};
|
|
1056
|
+
readonly seed: {
|
|
1057
|
+
readonly type: "int";
|
|
1058
|
+
readonly default: 7;
|
|
1059
|
+
readonly min: 0;
|
|
1060
|
+
readonly max: 999999;
|
|
1061
|
+
};
|
|
1062
|
+
};
|
|
1063
|
+
};
|
|
1064
|
+
loadExecute: () => Promise<typeof executeSimulate>;
|
|
1065
|
+
};
|
|
1066
|
+
export declare const sourceRegistration: {
|
|
1067
|
+
kind: "definition-v1";
|
|
1068
|
+
moduleId: string;
|
|
1069
|
+
definition: {
|
|
1070
|
+
readonly apiVersion: 1;
|
|
1071
|
+
readonly label: "POP Source";
|
|
1072
|
+
readonly description: "Birth particles from a geometry's points.";
|
|
1073
|
+
readonly icon: "Sparkles";
|
|
1074
|
+
readonly runsOn: "portable";
|
|
1075
|
+
readonly inputs: {
|
|
1076
|
+
readonly geometry: {
|
|
1077
|
+
readonly kind: "data";
|
|
1078
|
+
readonly type: "geometry";
|
|
1079
|
+
};
|
|
1080
|
+
readonly particles: {
|
|
1081
|
+
readonly kind: "data";
|
|
1082
|
+
readonly type: "geometry";
|
|
1083
|
+
};
|
|
1084
|
+
};
|
|
1085
|
+
readonly outputs: {
|
|
1086
|
+
readonly geometry: {
|
|
1087
|
+
readonly kind: "data";
|
|
1088
|
+
readonly type: "geometry";
|
|
1089
|
+
};
|
|
1090
|
+
};
|
|
1091
|
+
readonly props: {
|
|
1092
|
+
readonly impulse: {
|
|
1093
|
+
readonly type: "int";
|
|
1094
|
+
readonly default: 8;
|
|
1095
|
+
readonly min: 0;
|
|
1096
|
+
readonly max: 100000;
|
|
1097
|
+
};
|
|
1098
|
+
readonly life: {
|
|
1099
|
+
readonly type: "float";
|
|
1100
|
+
readonly default: 3;
|
|
1101
|
+
readonly min: 0;
|
|
1102
|
+
readonly max: 3600;
|
|
1103
|
+
readonly step: 0.1;
|
|
1104
|
+
};
|
|
1105
|
+
readonly lifevar: {
|
|
1106
|
+
readonly type: "float";
|
|
1107
|
+
readonly default: 0.3;
|
|
1108
|
+
readonly min: 0;
|
|
1109
|
+
readonly max: 1;
|
|
1110
|
+
readonly step: 0.01;
|
|
1111
|
+
};
|
|
1112
|
+
readonly velocity: {
|
|
1113
|
+
readonly type: "vec2";
|
|
1114
|
+
readonly default: readonly [0, 0];
|
|
1115
|
+
};
|
|
1116
|
+
readonly seed: {
|
|
1117
|
+
readonly type: "int";
|
|
1118
|
+
readonly default: 7;
|
|
1119
|
+
readonly min: 0;
|
|
1120
|
+
readonly max: 999999;
|
|
1121
|
+
};
|
|
1122
|
+
};
|
|
1123
|
+
};
|
|
1124
|
+
loadExecute: () => Promise<typeof executeSource>;
|
|
1125
|
+
};
|
|
1126
|
+
export declare const solverRegistration: {
|
|
1127
|
+
kind: "definition-v1";
|
|
1128
|
+
moduleId: string;
|
|
1129
|
+
definition: {
|
|
1130
|
+
readonly apiVersion: 1;
|
|
1131
|
+
readonly label: "POP Solver";
|
|
1132
|
+
readonly description: "Advance a particle system by one timestep.";
|
|
1133
|
+
readonly icon: "Play";
|
|
1134
|
+
readonly runsOn: "portable";
|
|
1135
|
+
readonly inputs: {
|
|
1136
|
+
readonly particles: {
|
|
1137
|
+
readonly kind: "data";
|
|
1138
|
+
readonly type: "geometry";
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
1141
|
+
readonly outputs: {
|
|
1142
|
+
readonly geometry: {
|
|
1143
|
+
readonly kind: "data";
|
|
1144
|
+
readonly type: "geometry";
|
|
1145
|
+
};
|
|
1146
|
+
};
|
|
1147
|
+
readonly props: {
|
|
1148
|
+
/**
|
|
1149
|
+
* Where the timestep comes from. Marcus's ruling, 2026-09-09: *"Let the
|
|
1150
|
+
* user choose nodes fixed or variable."*
|
|
1151
|
+
*
|
|
1152
|
+
* `fixed` is the default on the ordinary grounds that the safer of two
|
|
1153
|
+
* behaviours is the one you get without asking - a simulation whose result
|
|
1154
|
+
* depends on a playback setting is not reproducible from the document
|
|
1155
|
+
* alone. With `fps` the render rate becomes part of what determines the
|
|
1156
|
+
* output, which is correct for a mode called variable and must not be
|
|
1157
|
+
* silent: it belongs in the cache key, and `cascade check` should say so.
|
|
1158
|
+
*/
|
|
1159
|
+
readonly timestep_mode: {
|
|
1160
|
+
readonly type: "string";
|
|
1161
|
+
readonly default: "fixed";
|
|
1162
|
+
readonly control: "select";
|
|
1163
|
+
readonly options: readonly [{
|
|
1164
|
+
readonly value: "fixed";
|
|
1165
|
+
readonly label: "Fixed - reproducible from the document";
|
|
1166
|
+
}, {
|
|
1167
|
+
readonly value: "fps";
|
|
1168
|
+
readonly label: "Variable - derived from the render rate";
|
|
1169
|
+
}];
|
|
1170
|
+
};
|
|
1171
|
+
readonly timestep: {
|
|
1172
|
+
readonly type: "float";
|
|
1173
|
+
readonly default: number;
|
|
1174
|
+
readonly min: number;
|
|
1175
|
+
readonly max: 1;
|
|
1176
|
+
readonly step: number;
|
|
1177
|
+
};
|
|
1178
|
+
readonly maxspeed: {
|
|
1179
|
+
readonly type: "float";
|
|
1180
|
+
readonly default: 0;
|
|
1181
|
+
readonly min: 0;
|
|
1182
|
+
readonly max: 10000;
|
|
1183
|
+
readonly step: 0.5;
|
|
1184
|
+
};
|
|
1185
|
+
readonly force: {
|
|
1186
|
+
readonly type: "vec2";
|
|
1187
|
+
readonly default: readonly [0, 0];
|
|
1188
|
+
};
|
|
1189
|
+
readonly airresist: {
|
|
1190
|
+
readonly type: "float";
|
|
1191
|
+
readonly default: 0;
|
|
1192
|
+
readonly min: 0;
|
|
1193
|
+
readonly max: 20;
|
|
1194
|
+
readonly step: 0.05;
|
|
1195
|
+
};
|
|
1196
|
+
};
|
|
1197
|
+
};
|
|
1198
|
+
loadExecute: () => Promise<typeof executeSolver>;
|
|
1199
|
+
};
|
|
1200
|
+
export declare const noiseForceRegistration: {
|
|
1201
|
+
kind: "definition-v1";
|
|
1202
|
+
moduleId: string;
|
|
1203
|
+
definition: {
|
|
1204
|
+
readonly apiVersion: 1;
|
|
1205
|
+
readonly label: "POP Noise Force";
|
|
1206
|
+
readonly description: "Push particles along a heading read from a noise field.";
|
|
1207
|
+
readonly icon: "Wind";
|
|
1208
|
+
readonly runsOn: "portable";
|
|
1209
|
+
readonly inputs: {
|
|
1210
|
+
readonly particles: {
|
|
1211
|
+
readonly kind: "data";
|
|
1212
|
+
readonly type: "geometry";
|
|
1213
|
+
};
|
|
1214
|
+
};
|
|
1215
|
+
readonly outputs: {
|
|
1216
|
+
readonly geometry: {
|
|
1217
|
+
readonly kind: "data";
|
|
1218
|
+
readonly type: "geometry";
|
|
1219
|
+
};
|
|
1220
|
+
};
|
|
1221
|
+
readonly props: {
|
|
1222
|
+
readonly amplitude: {
|
|
1223
|
+
readonly type: "float";
|
|
1224
|
+
readonly default: 6;
|
|
1225
|
+
readonly min: 0;
|
|
1226
|
+
readonly max: 500;
|
|
1227
|
+
readonly step: 0.5;
|
|
1228
|
+
};
|
|
1229
|
+
readonly frequency: {
|
|
1230
|
+
readonly type: "float";
|
|
1231
|
+
readonly default: 0.3;
|
|
1232
|
+
readonly min: 0.001;
|
|
1233
|
+
readonly max: 10;
|
|
1234
|
+
readonly step: 0.01;
|
|
1235
|
+
};
|
|
1236
|
+
readonly evolve: {
|
|
1237
|
+
readonly type: "float";
|
|
1238
|
+
readonly default: 0;
|
|
1239
|
+
readonly min: -1000;
|
|
1240
|
+
readonly max: 1000;
|
|
1241
|
+
readonly step: 0.01;
|
|
1242
|
+
};
|
|
1243
|
+
readonly timestep: {
|
|
1244
|
+
readonly type: "float";
|
|
1245
|
+
readonly default: number;
|
|
1246
|
+
readonly min: number;
|
|
1247
|
+
readonly max: 1;
|
|
1248
|
+
readonly step: number;
|
|
1249
|
+
};
|
|
1250
|
+
readonly seed: {
|
|
1251
|
+
readonly type: "int";
|
|
1252
|
+
readonly default: 7;
|
|
1253
|
+
readonly min: 0;
|
|
1254
|
+
readonly max: 999999;
|
|
1255
|
+
};
|
|
1256
|
+
};
|
|
1257
|
+
};
|
|
1258
|
+
loadExecute: () => Promise<typeof executeNoiseForce>;
|
|
1259
|
+
};
|
|
1260
|
+
export declare const separateRegistration: {
|
|
1261
|
+
kind: "definition-v1";
|
|
1262
|
+
moduleId: string;
|
|
1263
|
+
definition: {
|
|
1264
|
+
readonly apiVersion: 1;
|
|
1265
|
+
readonly label: "POP Separate";
|
|
1266
|
+
readonly description: "Push neighbours apart within a radius.";
|
|
1267
|
+
readonly icon: "Move3d";
|
|
1268
|
+
readonly runsOn: "portable";
|
|
1269
|
+
readonly inputs: {
|
|
1270
|
+
readonly particles: {
|
|
1271
|
+
readonly kind: "data";
|
|
1272
|
+
readonly type: "geometry";
|
|
1273
|
+
};
|
|
1274
|
+
};
|
|
1275
|
+
readonly outputs: {
|
|
1276
|
+
readonly geometry: {
|
|
1277
|
+
readonly kind: "data";
|
|
1278
|
+
readonly type: "geometry";
|
|
1279
|
+
};
|
|
1280
|
+
};
|
|
1281
|
+
readonly props: {
|
|
1282
|
+
readonly radius: {
|
|
1283
|
+
readonly type: "float";
|
|
1284
|
+
readonly default: 0.5;
|
|
1285
|
+
readonly min: 0;
|
|
1286
|
+
readonly max: 100;
|
|
1287
|
+
readonly step: 0.01;
|
|
1288
|
+
};
|
|
1289
|
+
readonly strength: {
|
|
1290
|
+
readonly type: "float";
|
|
1291
|
+
readonly default: 4;
|
|
1292
|
+
readonly min: 0;
|
|
1293
|
+
readonly max: 500;
|
|
1294
|
+
readonly step: 0.1;
|
|
1295
|
+
};
|
|
1296
|
+
readonly timestep: {
|
|
1297
|
+
readonly type: "float";
|
|
1298
|
+
readonly default: number;
|
|
1299
|
+
readonly min: number;
|
|
1300
|
+
readonly max: 1;
|
|
1301
|
+
readonly step: number;
|
|
1302
|
+
};
|
|
1303
|
+
};
|
|
1304
|
+
};
|
|
1305
|
+
loadExecute: () => Promise<typeof executeSeparate>;
|
|
1306
|
+
};
|
|
1307
|
+
export declare const fieldForceRegistration: {
|
|
1308
|
+
kind: "definition-v1";
|
|
1309
|
+
moduleId: string;
|
|
1310
|
+
definition: {
|
|
1311
|
+
readonly apiVersion: 1;
|
|
1312
|
+
readonly label: "POP Field Force";
|
|
1313
|
+
readonly description: "Pull particles along a field's gradient and around its contours. The calligraphy force.";
|
|
1314
|
+
readonly icon: "Waves";
|
|
1315
|
+
readonly runsOn: "portable";
|
|
1316
|
+
readonly inputs: {
|
|
1317
|
+
readonly particles: {
|
|
1318
|
+
readonly kind: "data";
|
|
1319
|
+
readonly type: "geometry";
|
|
1320
|
+
};
|
|
1321
|
+
/** The field: points carrying `N` (the gradient) and optionally `level`. */
|
|
1322
|
+
readonly field: {
|
|
1323
|
+
readonly kind: "data";
|
|
1324
|
+
readonly type: "geometry";
|
|
1325
|
+
};
|
|
1326
|
+
};
|
|
1327
|
+
readonly outputs: {
|
|
1328
|
+
readonly geometry: {
|
|
1329
|
+
readonly kind: "data";
|
|
1330
|
+
readonly type: "geometry";
|
|
1331
|
+
};
|
|
1332
|
+
};
|
|
1333
|
+
readonly props: {
|
|
1334
|
+
readonly normal: {
|
|
1335
|
+
readonly type: "float";
|
|
1336
|
+
readonly default: 0;
|
|
1337
|
+
readonly min: -100000;
|
|
1338
|
+
readonly max: 100000;
|
|
1339
|
+
readonly label: "Normal";
|
|
1340
|
+
readonly description: "Along N, toward the spine. Negative pushes away.";
|
|
1341
|
+
};
|
|
1342
|
+
readonly tangential: {
|
|
1343
|
+
readonly type: "float";
|
|
1344
|
+
readonly default: 2600;
|
|
1345
|
+
readonly min: -100000;
|
|
1346
|
+
readonly max: 100000;
|
|
1347
|
+
readonly label: "Tangential";
|
|
1348
|
+
readonly description: "Perpendicular to N, around the outline. Sign picks the direction.";
|
|
1349
|
+
};
|
|
1350
|
+
readonly radius: {
|
|
1351
|
+
readonly type: "float";
|
|
1352
|
+
readonly default: 20;
|
|
1353
|
+
readonly min: 0;
|
|
1354
|
+
readonly max: 10000;
|
|
1355
|
+
readonly label: "Radius";
|
|
1356
|
+
readonly description: "How far a field sample reaches.";
|
|
1357
|
+
};
|
|
1358
|
+
readonly level: {
|
|
1359
|
+
readonly type: "float";
|
|
1360
|
+
readonly default: 0.28;
|
|
1361
|
+
readonly min: 0;
|
|
1362
|
+
readonly max: 1;
|
|
1363
|
+
readonly step: 0.01;
|
|
1364
|
+
readonly label: "Level";
|
|
1365
|
+
readonly description: "The contour to hold, rather than climbing to the peak.";
|
|
1366
|
+
};
|
|
1367
|
+
readonly hold: {
|
|
1368
|
+
readonly type: "float";
|
|
1369
|
+
readonly default: 5200;
|
|
1370
|
+
readonly min: 0;
|
|
1371
|
+
readonly max: 100000;
|
|
1372
|
+
readonly label: "Hold";
|
|
1373
|
+
readonly description: "How hard to hold that contour.";
|
|
1374
|
+
};
|
|
1375
|
+
};
|
|
1376
|
+
};
|
|
1377
|
+
loadExecute: () => Promise<typeof executeFieldForce>;
|
|
1378
|
+
};
|
|
1379
|
+
export declare const trailRegistration: {
|
|
1380
|
+
kind: "definition-v1";
|
|
1381
|
+
moduleId: string;
|
|
1382
|
+
definition: {
|
|
1383
|
+
readonly apiVersion: 1;
|
|
1384
|
+
readonly label: "POP Trail";
|
|
1385
|
+
readonly description: "Build trails from a Feedback container's history.";
|
|
1386
|
+
readonly icon: "Activity";
|
|
1387
|
+
readonly runsOn: "portable";
|
|
1388
|
+
readonly inputs: {
|
|
1389
|
+
/** The container's `history` output: every kept step, oldest first. */
|
|
1390
|
+
readonly history: {
|
|
1391
|
+
readonly kind: "data";
|
|
1392
|
+
readonly type: "array";
|
|
1393
|
+
};
|
|
1394
|
+
/** The current state, for `preserve` and for the newest point of each trail. */
|
|
1395
|
+
readonly particles: {
|
|
1396
|
+
readonly kind: "data";
|
|
1397
|
+
readonly type: "geometry";
|
|
1398
|
+
};
|
|
1399
|
+
};
|
|
1400
|
+
readonly outputs: {
|
|
1401
|
+
readonly geometry: {
|
|
1402
|
+
readonly kind: "data";
|
|
1403
|
+
readonly type: "geometry";
|
|
1404
|
+
};
|
|
1405
|
+
};
|
|
1406
|
+
readonly props: {
|
|
1407
|
+
readonly result: {
|
|
1408
|
+
readonly type: "string";
|
|
1409
|
+
readonly default: "polylines";
|
|
1410
|
+
readonly control: "select";
|
|
1411
|
+
readonly options: readonly [{
|
|
1412
|
+
readonly value: "polylines";
|
|
1413
|
+
readonly label: "Polylines - one line per particle";
|
|
1414
|
+
}, {
|
|
1415
|
+
readonly value: "preserve";
|
|
1416
|
+
readonly label: "Preserve - pass the points through";
|
|
1417
|
+
}];
|
|
1418
|
+
readonly label: "Result";
|
|
1419
|
+
};
|
|
1420
|
+
readonly length: {
|
|
1421
|
+
readonly type: "int";
|
|
1422
|
+
readonly default: 12;
|
|
1423
|
+
readonly min: 1;
|
|
1424
|
+
readonly max: 100000;
|
|
1425
|
+
readonly label: "Length";
|
|
1426
|
+
};
|
|
1427
|
+
readonly increment: {
|
|
1428
|
+
readonly type: "int";
|
|
1429
|
+
readonly default: 1;
|
|
1430
|
+
readonly min: 1;
|
|
1431
|
+
readonly max: 1000;
|
|
1432
|
+
readonly label: "Increment";
|
|
1433
|
+
};
|
|
1434
|
+
readonly velocityscale: {
|
|
1435
|
+
readonly type: "float";
|
|
1436
|
+
readonly default: 1;
|
|
1437
|
+
readonly min: 0;
|
|
1438
|
+
readonly max: 100;
|
|
1439
|
+
readonly step: 0.01;
|
|
1440
|
+
readonly label: "Velocity Scale";
|
|
1441
|
+
};
|
|
1442
|
+
};
|
|
1443
|
+
};
|
|
1444
|
+
loadExecute: () => Promise<typeof executeTrail>;
|
|
1445
|
+
};
|
|
1446
|
+
export declare const popNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
1447
|
+
/** `[moduleId, definition]` pairs, for a host building a node palette. The
|
|
1448
|
+
* same shape `geoNodeDefinitions` has, so Studio's library builder needs no
|
|
1449
|
+
* second branch. */
|
|
1450
|
+
export declare const popNodeDefinitions: readonly [readonly ["cascade.pop.Simulate", {
|
|
1451
|
+
readonly apiVersion: 1;
|
|
1452
|
+
readonly label: "POP Simulate";
|
|
1453
|
+
readonly description: "Re-simulate a particle system from frame zero to this frame.";
|
|
1454
|
+
readonly icon: "Rewind";
|
|
1455
|
+
readonly runsOn: "portable";
|
|
1456
|
+
readonly inputs: {
|
|
1457
|
+
/** Where particles are born. Its points are the birth positions. */
|
|
1458
|
+
readonly geometry: {
|
|
1459
|
+
readonly kind: "data";
|
|
1460
|
+
readonly type: "geometry";
|
|
1461
|
+
};
|
|
1462
|
+
/** The frame to simulate up to. Drive it with `$F`. An input rather than
|
|
1463
|
+
* a prop because the whole node is a function of it, and because a node
|
|
1464
|
+
* that read the clock itself would be the thing `AGENTS.md` forbids. */
|
|
1465
|
+
readonly frame: {
|
|
1466
|
+
readonly kind: "data";
|
|
1467
|
+
readonly type: "float";
|
|
1468
|
+
readonly default: 1;
|
|
1469
|
+
};
|
|
1470
|
+
/**
|
|
1471
|
+
* Points particles are drawn toward. Houdini's POP Attract.
|
|
1472
|
+
*
|
|
1473
|
+
* **Geometry rather than an image, and that was a correction.** The
|
|
1474
|
+
* original piece renders its word to a canvas, blurs it, reads the pixels
|
|
1475
|
+
* back and takes central differences — so its target really is a raster
|
|
1476
|
+
* field, and an `image` input was the first thing tried here. It was wrong
|
|
1477
|
+
* for this node: decoding an image needs a capability, the media
|
|
1478
|
+
* capability hands back a host-specific lease rather than a portable
|
|
1479
|
+
* raster, and a POP node doing IO stops being the pure arithmetic that
|
|
1480
|
+
* makes it cook identically in both hosts.
|
|
1481
|
+
*
|
|
1482
|
+
* So the rasterising belongs upstream, in whatever node knows about text
|
|
1483
|
+
* or photographs, and what reaches the solver is a set of target points.
|
|
1484
|
+
* `fieldForce` in `pop/forces.ts` still takes a sampled raster for a node
|
|
1485
|
+
* that has one; this input is the composable half.
|
|
1486
|
+
*/
|
|
1487
|
+
readonly attract: {
|
|
1488
|
+
readonly kind: "data";
|
|
1489
|
+
readonly type: "geometry";
|
|
1490
|
+
};
|
|
1491
|
+
/**
|
|
1492
|
+
* A flow field, as points carrying `N`.
|
|
1493
|
+
*
|
|
1494
|
+
* Marcus's description of the original: *"a delicate balance between a
|
|
1495
|
+
* force pulling the particles towards the centre spine of the type and
|
|
1496
|
+
* tangentially around their outlines."* This is that field — the gradient
|
|
1497
|
+
* of a BLURRED letterform, whose ridge runs down each stroke's middle, so
|
|
1498
|
+
* the gradient points at the spine and its perpendicular runs along the
|
|
1499
|
+
* outline. `field_normal` and `field_tangential` are the two halves of the
|
|
1500
|
+
* balance.
|
|
1501
|
+
*/
|
|
1502
|
+
readonly field: {
|
|
1503
|
+
readonly kind: "data";
|
|
1504
|
+
readonly type: "geometry";
|
|
1505
|
+
};
|
|
1506
|
+
};
|
|
1507
|
+
readonly outputs: {
|
|
1508
|
+
/** The particles, one point each. */
|
|
1509
|
+
readonly geometry: {
|
|
1510
|
+
readonly kind: "data";
|
|
1511
|
+
readonly type: "geometry";
|
|
1512
|
+
};
|
|
1513
|
+
/**
|
|
1514
|
+
* The trails, one open polyline per particle.
|
|
1515
|
+
*
|
|
1516
|
+
* Emitted here rather than by a standalone `Trail` node, and that is a
|
|
1517
|
+
* consequence of re-simulation rather than a shortcut. Houdini's Trail SOP
|
|
1518
|
+
* reads a rolling window the cook already holds, because Houdini's cook is
|
|
1519
|
+
* stateful; Cascade's node is a pure function of its inputs, so a
|
|
1520
|
+
* standalone `Trail` can only see the frame on its port — one frame, which
|
|
1521
|
+
* draws nothing. This node replays every frame from zero, so it has the
|
|
1522
|
+
* whole history for free, and a node that draws nothing would be worse than
|
|
1523
|
+
* no node.
|
|
1524
|
+
*
|
|
1525
|
+
* When `CookScheduler` owns the rolling window the plan describes,
|
|
1526
|
+
* `cascade.pop.Trail` becomes a real node and this output stays as the
|
|
1527
|
+
* cheap path. The parameters are Houdini's either way.
|
|
1528
|
+
*/
|
|
1529
|
+
readonly trails: {
|
|
1530
|
+
readonly kind: "data";
|
|
1531
|
+
readonly type: "geometry";
|
|
1532
|
+
};
|
|
1533
|
+
};
|
|
1534
|
+
readonly props: {
|
|
1535
|
+
/** Frames of history in the trails. Houdini's Trail `length`. */
|
|
1536
|
+
readonly trail_length: {
|
|
1537
|
+
readonly type: "int";
|
|
1538
|
+
readonly default: 14;
|
|
1539
|
+
readonly min: 1;
|
|
1540
|
+
readonly max: 500;
|
|
1541
|
+
};
|
|
1542
|
+
/** Use every nth frame. Houdini's `increment`. */
|
|
1543
|
+
readonly trail_increment: {
|
|
1544
|
+
readonly type: "int";
|
|
1545
|
+
readonly default: 1;
|
|
1546
|
+
readonly min: 1;
|
|
1547
|
+
readonly max: 32;
|
|
1548
|
+
};
|
|
1549
|
+
/**
|
|
1550
|
+
* Re-simulation, and why this is the first implementation rather than a
|
|
1551
|
+
* placeholder for a cached one.
|
|
1552
|
+
*
|
|
1553
|
+
* `PLAN particles.md`: re-simulation from the start frame is the
|
|
1554
|
+
* *definition* of correctness, and a cache is an accelerator holding
|
|
1555
|
+
* nothing re-simulation would not have produced. So this node is the
|
|
1556
|
+
* reference: it reads a frame, replays every step from zero, and depends on
|
|
1557
|
+
* no state whatsoever. Scrubbing is O(frame) and that is the honest cost —
|
|
1558
|
+
* a checkpoint cache makes it fast later without changing a single output,
|
|
1559
|
+
* which is exactly the property a cache should have.
|
|
1560
|
+
*/
|
|
1561
|
+
readonly substeps: {
|
|
1562
|
+
readonly type: "int";
|
|
1563
|
+
readonly default: 1;
|
|
1564
|
+
readonly min: 1;
|
|
1565
|
+
readonly max: 32;
|
|
1566
|
+
};
|
|
1567
|
+
readonly timestep: {
|
|
1568
|
+
readonly type: "float";
|
|
1569
|
+
readonly default: number;
|
|
1570
|
+
readonly min: number;
|
|
1571
|
+
readonly max: 1;
|
|
1572
|
+
readonly step: number;
|
|
1573
|
+
};
|
|
1574
|
+
/** Per frame, as on Source. */
|
|
1575
|
+
readonly impulse: {
|
|
1576
|
+
readonly type: "int";
|
|
1577
|
+
readonly default: 6;
|
|
1578
|
+
readonly min: 0;
|
|
1579
|
+
readonly max: 100000;
|
|
1580
|
+
};
|
|
1581
|
+
readonly life: {
|
|
1582
|
+
readonly type: "float";
|
|
1583
|
+
readonly default: 4;
|
|
1584
|
+
readonly min: 0;
|
|
1585
|
+
readonly max: 3600;
|
|
1586
|
+
readonly step: 0.1;
|
|
1587
|
+
};
|
|
1588
|
+
readonly lifevar: {
|
|
1589
|
+
readonly type: "float";
|
|
1590
|
+
readonly default: 0.4;
|
|
1591
|
+
readonly min: 0;
|
|
1592
|
+
readonly max: 1;
|
|
1593
|
+
readonly step: 0.01;
|
|
1594
|
+
};
|
|
1595
|
+
readonly force: {
|
|
1596
|
+
readonly type: "vec2";
|
|
1597
|
+
readonly default: readonly [0, 0];
|
|
1598
|
+
};
|
|
1599
|
+
readonly airresist: {
|
|
1600
|
+
readonly type: "float";
|
|
1601
|
+
readonly default: 0.4;
|
|
1602
|
+
readonly min: 0;
|
|
1603
|
+
readonly max: 20;
|
|
1604
|
+
readonly step: 0.05;
|
|
1605
|
+
};
|
|
1606
|
+
readonly noise_amplitude: {
|
|
1607
|
+
readonly type: "float";
|
|
1608
|
+
readonly default: 14;
|
|
1609
|
+
readonly min: 0;
|
|
1610
|
+
readonly max: 500;
|
|
1611
|
+
readonly step: 0.5;
|
|
1612
|
+
};
|
|
1613
|
+
readonly noise_frequency: {
|
|
1614
|
+
readonly type: "float";
|
|
1615
|
+
readonly default: 0.02;
|
|
1616
|
+
readonly min: 0.001;
|
|
1617
|
+
readonly max: 10;
|
|
1618
|
+
readonly step: 0.001;
|
|
1619
|
+
};
|
|
1620
|
+
/** Turns per second of field evolution. */
|
|
1621
|
+
readonly noise_evolve: {
|
|
1622
|
+
readonly type: "float";
|
|
1623
|
+
readonly default: 0.08;
|
|
1624
|
+
readonly min: -10;
|
|
1625
|
+
readonly max: 10;
|
|
1626
|
+
readonly step: 0.01;
|
|
1627
|
+
};
|
|
1628
|
+
/** Toward the spine, up the field's gradient. */
|
|
1629
|
+
readonly field_normal: {
|
|
1630
|
+
readonly type: "float";
|
|
1631
|
+
readonly default: 0;
|
|
1632
|
+
readonly min: -20000;
|
|
1633
|
+
readonly max: 20000;
|
|
1634
|
+
readonly step: 1;
|
|
1635
|
+
};
|
|
1636
|
+
/** Around the outline, perpendicular to the gradient. Sign picks which way. */
|
|
1637
|
+
readonly field_tangential: {
|
|
1638
|
+
readonly type: "float";
|
|
1639
|
+
readonly default: 0;
|
|
1640
|
+
readonly min: -20000;
|
|
1641
|
+
readonly max: 20000;
|
|
1642
|
+
readonly step: 1;
|
|
1643
|
+
};
|
|
1644
|
+
/** Field samples beyond this are ignored, and it is the grid's cell size. */
|
|
1645
|
+
readonly field_radius: {
|
|
1646
|
+
readonly type: "float";
|
|
1647
|
+
readonly default: 24;
|
|
1648
|
+
readonly min: 0.5;
|
|
1649
|
+
readonly max: 2000;
|
|
1650
|
+
readonly step: 0.5;
|
|
1651
|
+
};
|
|
1652
|
+
/**
|
|
1653
|
+
* The contour to hold, as a field level 0..1, with `field_hold` as how
|
|
1654
|
+
* firmly.
|
|
1655
|
+
*
|
|
1656
|
+
* Marcus: *"the lines flow tangentially around the letters in a bundle …
|
|
1657
|
+
* loosely describing the type."* A bundle is a contour, so holding a level
|
|
1658
|
+
* and letting the tangent carry particles round it is the mechanism —
|
|
1659
|
+
* pulling them to the spine instead collapses each letter to a point, and
|
|
1660
|
+
* pure tangential flow lets every particle converge onto one level set.
|
|
1661
|
+
*/
|
|
1662
|
+
readonly field_level: {
|
|
1663
|
+
readonly type: "float";
|
|
1664
|
+
readonly default: 0.35;
|
|
1665
|
+
readonly min: 0;
|
|
1666
|
+
readonly max: 1;
|
|
1667
|
+
readonly step: 0.01;
|
|
1668
|
+
};
|
|
1669
|
+
readonly field_hold: {
|
|
1670
|
+
readonly type: "float";
|
|
1671
|
+
readonly default: 0;
|
|
1672
|
+
readonly min: 0;
|
|
1673
|
+
readonly max: 40000;
|
|
1674
|
+
readonly step: 10;
|
|
1675
|
+
};
|
|
1676
|
+
/**
|
|
1677
|
+
* Scatter births across this box instead of on the source geometry's
|
|
1678
|
+
* points, `[minX, minY, maxX, maxY]`. All zero uses the points.
|
|
1679
|
+
*
|
|
1680
|
+
* Houdini's POP Source has the same choice — *Scatter onto Surface* against
|
|
1681
|
+
* *Points* — and Marcus's note is that the original spawns *around* the
|
|
1682
|
+
* type rather than on it: *"The particles should spawn in the area around
|
|
1683
|
+
* the type … and then the forces should drag them around."* Birthing on
|
|
1684
|
+
* the letterforms puts every particle where it is already going, so
|
|
1685
|
+
* nothing travels and the field has nothing to reveal.
|
|
1686
|
+
*/
|
|
1687
|
+
readonly birth_area: {
|
|
1688
|
+
readonly type: "vec4";
|
|
1689
|
+
readonly default: readonly [0, 0, 0, 0];
|
|
1690
|
+
};
|
|
1691
|
+
/**
|
|
1692
|
+
* A colour per particle, drawn at birth and fixed for its life.
|
|
1693
|
+
*
|
|
1694
|
+
* Hashed on the id, so the same particle gets the same colour however many
|
|
1695
|
+
* times the frame is re-simulated — a colour drawn from a stream would
|
|
1696
|
+
* change on every scrub, which reads as the render being unstable.
|
|
1697
|
+
*
|
|
1698
|
+
* `[from, to]` in turns around the wheel. Equal values give one hue; a
|
|
1699
|
+
* narrow span is usually what a piece wants, because a full-spectrum
|
|
1700
|
+
* random set reads as a test pattern rather than as a palette.
|
|
1701
|
+
*/
|
|
1702
|
+
readonly birth_hue: {
|
|
1703
|
+
readonly type: "vec2";
|
|
1704
|
+
readonly default: readonly [0, 0];
|
|
1705
|
+
readonly min: 0;
|
|
1706
|
+
readonly max: 1;
|
|
1707
|
+
readonly step: 0.01;
|
|
1708
|
+
};
|
|
1709
|
+
readonly birth_saturation: {
|
|
1710
|
+
readonly type: "float";
|
|
1711
|
+
readonly default: 0.6;
|
|
1712
|
+
readonly min: 0;
|
|
1713
|
+
readonly max: 1;
|
|
1714
|
+
readonly step: 0.01;
|
|
1715
|
+
};
|
|
1716
|
+
readonly birth_value: {
|
|
1717
|
+
readonly type: "float";
|
|
1718
|
+
readonly default: 1;
|
|
1719
|
+
readonly min: 0;
|
|
1720
|
+
readonly max: 1;
|
|
1721
|
+
readonly step: 0.01;
|
|
1722
|
+
};
|
|
1723
|
+
/** How hard the targets pull. Houdini's POP Attract strength. */
|
|
1724
|
+
readonly attract_amplitude: {
|
|
1725
|
+
readonly type: "float";
|
|
1726
|
+
readonly default: 0;
|
|
1727
|
+
readonly min: 0;
|
|
1728
|
+
readonly max: 20000;
|
|
1729
|
+
readonly step: 1;
|
|
1730
|
+
};
|
|
1731
|
+
/** Beyond this distance a target is ignored, which is what keeps the pull
|
|
1732
|
+
* local and the cost bounded. 0 means every target pulls every particle. */
|
|
1733
|
+
readonly attract_radius: {
|
|
1734
|
+
readonly type: "float";
|
|
1735
|
+
readonly default: 60;
|
|
1736
|
+
readonly min: 0;
|
|
1737
|
+
readonly max: 5000;
|
|
1738
|
+
readonly step: 1;
|
|
1739
|
+
};
|
|
1740
|
+
readonly separate_radius: {
|
|
1741
|
+
readonly type: "float";
|
|
1742
|
+
readonly default: 0;
|
|
1743
|
+
readonly min: 0;
|
|
1744
|
+
readonly max: 500;
|
|
1745
|
+
readonly step: 0.5;
|
|
1746
|
+
};
|
|
1747
|
+
readonly separate_strength: {
|
|
1748
|
+
readonly type: "float";
|
|
1749
|
+
readonly default: 12;
|
|
1750
|
+
readonly min: 0;
|
|
1751
|
+
readonly max: 500;
|
|
1752
|
+
readonly step: 0.5;
|
|
1753
|
+
};
|
|
1754
|
+
readonly maxspeed: {
|
|
1755
|
+
readonly type: "float";
|
|
1756
|
+
readonly default: 60;
|
|
1757
|
+
readonly min: 0;
|
|
1758
|
+
readonly max: 10000;
|
|
1759
|
+
readonly step: 1;
|
|
1760
|
+
};
|
|
1761
|
+
/** `[minX, minY, maxX, maxY]`, or all zero for open space. The original
|
|
1762
|
+
* piece is toroidal. */
|
|
1763
|
+
readonly wrap: {
|
|
1764
|
+
readonly type: "vec4";
|
|
1765
|
+
readonly default: readonly [0, 0, 0, 0];
|
|
1766
|
+
};
|
|
1767
|
+
readonly seed: {
|
|
1768
|
+
readonly type: "int";
|
|
1769
|
+
readonly default: 7;
|
|
1770
|
+
readonly min: 0;
|
|
1771
|
+
readonly max: 999999;
|
|
1772
|
+
};
|
|
1773
|
+
};
|
|
1774
|
+
}], readonly ["cascade.pop.Source", {
|
|
1775
|
+
readonly apiVersion: 1;
|
|
1776
|
+
readonly label: "POP Source";
|
|
1777
|
+
readonly description: "Birth particles from a geometry's points.";
|
|
1778
|
+
readonly icon: "Sparkles";
|
|
1779
|
+
readonly runsOn: "portable";
|
|
1780
|
+
readonly inputs: {
|
|
1781
|
+
readonly geometry: {
|
|
1782
|
+
readonly kind: "data";
|
|
1783
|
+
readonly type: "geometry";
|
|
1784
|
+
};
|
|
1785
|
+
readonly particles: {
|
|
1786
|
+
readonly kind: "data";
|
|
1787
|
+
readonly type: "geometry";
|
|
1788
|
+
};
|
|
1789
|
+
};
|
|
1790
|
+
readonly outputs: {
|
|
1791
|
+
readonly geometry: {
|
|
1792
|
+
readonly kind: "data";
|
|
1793
|
+
readonly type: "geometry";
|
|
1794
|
+
};
|
|
1795
|
+
};
|
|
1796
|
+
readonly props: {
|
|
1797
|
+
readonly impulse: {
|
|
1798
|
+
readonly type: "int";
|
|
1799
|
+
readonly default: 8;
|
|
1800
|
+
readonly min: 0;
|
|
1801
|
+
readonly max: 100000;
|
|
1802
|
+
};
|
|
1803
|
+
readonly life: {
|
|
1804
|
+
readonly type: "float";
|
|
1805
|
+
readonly default: 3;
|
|
1806
|
+
readonly min: 0;
|
|
1807
|
+
readonly max: 3600;
|
|
1808
|
+
readonly step: 0.1;
|
|
1809
|
+
};
|
|
1810
|
+
readonly lifevar: {
|
|
1811
|
+
readonly type: "float";
|
|
1812
|
+
readonly default: 0.3;
|
|
1813
|
+
readonly min: 0;
|
|
1814
|
+
readonly max: 1;
|
|
1815
|
+
readonly step: 0.01;
|
|
1816
|
+
};
|
|
1817
|
+
readonly velocity: {
|
|
1818
|
+
readonly type: "vec2";
|
|
1819
|
+
readonly default: readonly [0, 0];
|
|
1820
|
+
};
|
|
1821
|
+
readonly seed: {
|
|
1822
|
+
readonly type: "int";
|
|
1823
|
+
readonly default: 7;
|
|
1824
|
+
readonly min: 0;
|
|
1825
|
+
readonly max: 999999;
|
|
1826
|
+
};
|
|
1827
|
+
};
|
|
1828
|
+
}], readonly ["cascade.pop.Solver", {
|
|
1829
|
+
readonly apiVersion: 1;
|
|
1830
|
+
readonly label: "POP Solver";
|
|
1831
|
+
readonly description: "Advance a particle system by one timestep.";
|
|
1832
|
+
readonly icon: "Play";
|
|
1833
|
+
readonly runsOn: "portable";
|
|
1834
|
+
readonly inputs: {
|
|
1835
|
+
readonly particles: {
|
|
1836
|
+
readonly kind: "data";
|
|
1837
|
+
readonly type: "geometry";
|
|
1838
|
+
};
|
|
1839
|
+
};
|
|
1840
|
+
readonly outputs: {
|
|
1841
|
+
readonly geometry: {
|
|
1842
|
+
readonly kind: "data";
|
|
1843
|
+
readonly type: "geometry";
|
|
1844
|
+
};
|
|
1845
|
+
};
|
|
1846
|
+
readonly props: {
|
|
1847
|
+
/**
|
|
1848
|
+
* Where the timestep comes from. Marcus's ruling, 2026-09-09: *"Let the
|
|
1849
|
+
* user choose nodes fixed or variable."*
|
|
1850
|
+
*
|
|
1851
|
+
* `fixed` is the default on the ordinary grounds that the safer of two
|
|
1852
|
+
* behaviours is the one you get without asking - a simulation whose result
|
|
1853
|
+
* depends on a playback setting is not reproducible from the document
|
|
1854
|
+
* alone. With `fps` the render rate becomes part of what determines the
|
|
1855
|
+
* output, which is correct for a mode called variable and must not be
|
|
1856
|
+
* silent: it belongs in the cache key, and `cascade check` should say so.
|
|
1857
|
+
*/
|
|
1858
|
+
readonly timestep_mode: {
|
|
1859
|
+
readonly type: "string";
|
|
1860
|
+
readonly default: "fixed";
|
|
1861
|
+
readonly control: "select";
|
|
1862
|
+
readonly options: readonly [{
|
|
1863
|
+
readonly value: "fixed";
|
|
1864
|
+
readonly label: "Fixed - reproducible from the document";
|
|
1865
|
+
}, {
|
|
1866
|
+
readonly value: "fps";
|
|
1867
|
+
readonly label: "Variable - derived from the render rate";
|
|
1868
|
+
}];
|
|
1869
|
+
};
|
|
1870
|
+
readonly timestep: {
|
|
1871
|
+
readonly type: "float";
|
|
1872
|
+
readonly default: number;
|
|
1873
|
+
readonly min: number;
|
|
1874
|
+
readonly max: 1;
|
|
1875
|
+
readonly step: number;
|
|
1876
|
+
};
|
|
1877
|
+
readonly maxspeed: {
|
|
1878
|
+
readonly type: "float";
|
|
1879
|
+
readonly default: 0;
|
|
1880
|
+
readonly min: 0;
|
|
1881
|
+
readonly max: 10000;
|
|
1882
|
+
readonly step: 0.5;
|
|
1883
|
+
};
|
|
1884
|
+
readonly force: {
|
|
1885
|
+
readonly type: "vec2";
|
|
1886
|
+
readonly default: readonly [0, 0];
|
|
1887
|
+
};
|
|
1888
|
+
readonly airresist: {
|
|
1889
|
+
readonly type: "float";
|
|
1890
|
+
readonly default: 0;
|
|
1891
|
+
readonly min: 0;
|
|
1892
|
+
readonly max: 20;
|
|
1893
|
+
readonly step: 0.05;
|
|
1894
|
+
};
|
|
1895
|
+
};
|
|
1896
|
+
}], readonly ["cascade.pop.NoiseForce", {
|
|
1897
|
+
readonly apiVersion: 1;
|
|
1898
|
+
readonly label: "POP Noise Force";
|
|
1899
|
+
readonly description: "Push particles along a heading read from a noise field.";
|
|
1900
|
+
readonly icon: "Wind";
|
|
1901
|
+
readonly runsOn: "portable";
|
|
1902
|
+
readonly inputs: {
|
|
1903
|
+
readonly particles: {
|
|
1904
|
+
readonly kind: "data";
|
|
1905
|
+
readonly type: "geometry";
|
|
1906
|
+
};
|
|
1907
|
+
};
|
|
1908
|
+
readonly outputs: {
|
|
1909
|
+
readonly geometry: {
|
|
1910
|
+
readonly kind: "data";
|
|
1911
|
+
readonly type: "geometry";
|
|
1912
|
+
};
|
|
1913
|
+
};
|
|
1914
|
+
readonly props: {
|
|
1915
|
+
readonly amplitude: {
|
|
1916
|
+
readonly type: "float";
|
|
1917
|
+
readonly default: 6;
|
|
1918
|
+
readonly min: 0;
|
|
1919
|
+
readonly max: 500;
|
|
1920
|
+
readonly step: 0.5;
|
|
1921
|
+
};
|
|
1922
|
+
readonly frequency: {
|
|
1923
|
+
readonly type: "float";
|
|
1924
|
+
readonly default: 0.3;
|
|
1925
|
+
readonly min: 0.001;
|
|
1926
|
+
readonly max: 10;
|
|
1927
|
+
readonly step: 0.01;
|
|
1928
|
+
};
|
|
1929
|
+
readonly evolve: {
|
|
1930
|
+
readonly type: "float";
|
|
1931
|
+
readonly default: 0;
|
|
1932
|
+
readonly min: -1000;
|
|
1933
|
+
readonly max: 1000;
|
|
1934
|
+
readonly step: 0.01;
|
|
1935
|
+
};
|
|
1936
|
+
readonly timestep: {
|
|
1937
|
+
readonly type: "float";
|
|
1938
|
+
readonly default: number;
|
|
1939
|
+
readonly min: number;
|
|
1940
|
+
readonly max: 1;
|
|
1941
|
+
readonly step: number;
|
|
1942
|
+
};
|
|
1943
|
+
readonly seed: {
|
|
1944
|
+
readonly type: "int";
|
|
1945
|
+
readonly default: 7;
|
|
1946
|
+
readonly min: 0;
|
|
1947
|
+
readonly max: 999999;
|
|
1948
|
+
};
|
|
1949
|
+
};
|
|
1950
|
+
}], readonly ["cascade.pop.Separate", {
|
|
1951
|
+
readonly apiVersion: 1;
|
|
1952
|
+
readonly label: "POP Separate";
|
|
1953
|
+
readonly description: "Push neighbours apart within a radius.";
|
|
1954
|
+
readonly icon: "Move3d";
|
|
1955
|
+
readonly runsOn: "portable";
|
|
1956
|
+
readonly inputs: {
|
|
1957
|
+
readonly particles: {
|
|
1958
|
+
readonly kind: "data";
|
|
1959
|
+
readonly type: "geometry";
|
|
1960
|
+
};
|
|
1961
|
+
};
|
|
1962
|
+
readonly outputs: {
|
|
1963
|
+
readonly geometry: {
|
|
1964
|
+
readonly kind: "data";
|
|
1965
|
+
readonly type: "geometry";
|
|
1966
|
+
};
|
|
1967
|
+
};
|
|
1968
|
+
readonly props: {
|
|
1969
|
+
readonly radius: {
|
|
1970
|
+
readonly type: "float";
|
|
1971
|
+
readonly default: 0.5;
|
|
1972
|
+
readonly min: 0;
|
|
1973
|
+
readonly max: 100;
|
|
1974
|
+
readonly step: 0.01;
|
|
1975
|
+
};
|
|
1976
|
+
readonly strength: {
|
|
1977
|
+
readonly type: "float";
|
|
1978
|
+
readonly default: 4;
|
|
1979
|
+
readonly min: 0;
|
|
1980
|
+
readonly max: 500;
|
|
1981
|
+
readonly step: 0.1;
|
|
1982
|
+
};
|
|
1983
|
+
readonly timestep: {
|
|
1984
|
+
readonly type: "float";
|
|
1985
|
+
readonly default: number;
|
|
1986
|
+
readonly min: number;
|
|
1987
|
+
readonly max: 1;
|
|
1988
|
+
readonly step: number;
|
|
1989
|
+
};
|
|
1990
|
+
};
|
|
1991
|
+
}], readonly ["cascade.pop.FieldForce", {
|
|
1992
|
+
readonly apiVersion: 1;
|
|
1993
|
+
readonly label: "POP Field Force";
|
|
1994
|
+
readonly description: "Pull particles along a field's gradient and around its contours. The calligraphy force.";
|
|
1995
|
+
readonly icon: "Waves";
|
|
1996
|
+
readonly runsOn: "portable";
|
|
1997
|
+
readonly inputs: {
|
|
1998
|
+
readonly particles: {
|
|
1999
|
+
readonly kind: "data";
|
|
2000
|
+
readonly type: "geometry";
|
|
2001
|
+
};
|
|
2002
|
+
/** The field: points carrying `N` (the gradient) and optionally `level`. */
|
|
2003
|
+
readonly field: {
|
|
2004
|
+
readonly kind: "data";
|
|
2005
|
+
readonly type: "geometry";
|
|
2006
|
+
};
|
|
2007
|
+
};
|
|
2008
|
+
readonly outputs: {
|
|
2009
|
+
readonly geometry: {
|
|
2010
|
+
readonly kind: "data";
|
|
2011
|
+
readonly type: "geometry";
|
|
2012
|
+
};
|
|
2013
|
+
};
|
|
2014
|
+
readonly props: {
|
|
2015
|
+
readonly normal: {
|
|
2016
|
+
readonly type: "float";
|
|
2017
|
+
readonly default: 0;
|
|
2018
|
+
readonly min: -100000;
|
|
2019
|
+
readonly max: 100000;
|
|
2020
|
+
readonly label: "Normal";
|
|
2021
|
+
readonly description: "Along N, toward the spine. Negative pushes away.";
|
|
2022
|
+
};
|
|
2023
|
+
readonly tangential: {
|
|
2024
|
+
readonly type: "float";
|
|
2025
|
+
readonly default: 2600;
|
|
2026
|
+
readonly min: -100000;
|
|
2027
|
+
readonly max: 100000;
|
|
2028
|
+
readonly label: "Tangential";
|
|
2029
|
+
readonly description: "Perpendicular to N, around the outline. Sign picks the direction.";
|
|
2030
|
+
};
|
|
2031
|
+
readonly radius: {
|
|
2032
|
+
readonly type: "float";
|
|
2033
|
+
readonly default: 20;
|
|
2034
|
+
readonly min: 0;
|
|
2035
|
+
readonly max: 10000;
|
|
2036
|
+
readonly label: "Radius";
|
|
2037
|
+
readonly description: "How far a field sample reaches.";
|
|
2038
|
+
};
|
|
2039
|
+
readonly level: {
|
|
2040
|
+
readonly type: "float";
|
|
2041
|
+
readonly default: 0.28;
|
|
2042
|
+
readonly min: 0;
|
|
2043
|
+
readonly max: 1;
|
|
2044
|
+
readonly step: 0.01;
|
|
2045
|
+
readonly label: "Level";
|
|
2046
|
+
readonly description: "The contour to hold, rather than climbing to the peak.";
|
|
2047
|
+
};
|
|
2048
|
+
readonly hold: {
|
|
2049
|
+
readonly type: "float";
|
|
2050
|
+
readonly default: 5200;
|
|
2051
|
+
readonly min: 0;
|
|
2052
|
+
readonly max: 100000;
|
|
2053
|
+
readonly label: "Hold";
|
|
2054
|
+
readonly description: "How hard to hold that contour.";
|
|
2055
|
+
};
|
|
2056
|
+
};
|
|
2057
|
+
}], readonly ["cascade.pop.Trail", {
|
|
2058
|
+
readonly apiVersion: 1;
|
|
2059
|
+
readonly label: "POP Trail";
|
|
2060
|
+
readonly description: "Build trails from a Feedback container's history.";
|
|
2061
|
+
readonly icon: "Activity";
|
|
2062
|
+
readonly runsOn: "portable";
|
|
2063
|
+
readonly inputs: {
|
|
2064
|
+
/** The container's `history` output: every kept step, oldest first. */
|
|
2065
|
+
readonly history: {
|
|
2066
|
+
readonly kind: "data";
|
|
2067
|
+
readonly type: "array";
|
|
2068
|
+
};
|
|
2069
|
+
/** The current state, for `preserve` and for the newest point of each trail. */
|
|
2070
|
+
readonly particles: {
|
|
2071
|
+
readonly kind: "data";
|
|
2072
|
+
readonly type: "geometry";
|
|
2073
|
+
};
|
|
2074
|
+
};
|
|
2075
|
+
readonly outputs: {
|
|
2076
|
+
readonly geometry: {
|
|
2077
|
+
readonly kind: "data";
|
|
2078
|
+
readonly type: "geometry";
|
|
2079
|
+
};
|
|
2080
|
+
};
|
|
2081
|
+
readonly props: {
|
|
2082
|
+
readonly result: {
|
|
2083
|
+
readonly type: "string";
|
|
2084
|
+
readonly default: "polylines";
|
|
2085
|
+
readonly control: "select";
|
|
2086
|
+
readonly options: readonly [{
|
|
2087
|
+
readonly value: "polylines";
|
|
2088
|
+
readonly label: "Polylines - one line per particle";
|
|
2089
|
+
}, {
|
|
2090
|
+
readonly value: "preserve";
|
|
2091
|
+
readonly label: "Preserve - pass the points through";
|
|
2092
|
+
}];
|
|
2093
|
+
readonly label: "Result";
|
|
2094
|
+
};
|
|
2095
|
+
readonly length: {
|
|
2096
|
+
readonly type: "int";
|
|
2097
|
+
readonly default: 12;
|
|
2098
|
+
readonly min: 1;
|
|
2099
|
+
readonly max: 100000;
|
|
2100
|
+
readonly label: "Length";
|
|
2101
|
+
};
|
|
2102
|
+
readonly increment: {
|
|
2103
|
+
readonly type: "int";
|
|
2104
|
+
readonly default: 1;
|
|
2105
|
+
readonly min: 1;
|
|
2106
|
+
readonly max: 1000;
|
|
2107
|
+
readonly label: "Increment";
|
|
2108
|
+
};
|
|
2109
|
+
readonly velocityscale: {
|
|
2110
|
+
readonly type: "float";
|
|
2111
|
+
readonly default: 1;
|
|
2112
|
+
readonly min: 0;
|
|
2113
|
+
readonly max: 100;
|
|
2114
|
+
readonly step: 0.01;
|
|
2115
|
+
readonly label: "Velocity Scale";
|
|
2116
|
+
};
|
|
2117
|
+
};
|
|
2118
|
+
}]];
|
|
2119
|
+
export {};
|
|
2120
|
+
//# sourceMappingURL=definitions.d.ts.map
|