@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.
Files changed (110) hide show
  1. package/AGENTS.md +18 -0
  2. package/ARCHITECTURE.md +53 -7
  3. package/CHANGELOG.md +47 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/DESIGN.md +9 -6
  6. package/README.md +80 -4
  7. package/dist/assets/{CodeEditor-BNOJIDzL.js → CodeEditor-dOrU3aQg.js} +7 -7
  8. package/dist/assets/{CodePanel-2wp2G0Fj.js → CodePanel-Bqy85qen.js} +2 -2
  9. package/dist/assets/{CustomNodeDialog-DNqaVBqA.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
  10. package/dist/assets/{ProjectSettings-DcdYF2E1.js → ProjectSettings-D192mr6g.js} +2 -2
  11. package/dist/assets/{SettingsDialog-D2xVvyiR.js → SettingsDialog-BTeeIE3T.js} +1 -1
  12. package/dist/assets/{VersionHistoryDialog-DW288ykF.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
  13. package/dist/assets/{core-B1y38i95.js → core-DwbcGL0o.js} +2 -2
  14. package/dist/assets/{cssMode-BLucnaln.js → cssMode-Dcjn356x.js} +1 -1
  15. package/dist/assets/definitions-C_SY6Bhg.js +1 -0
  16. package/dist/assets/{editor.api-B3Et3Pph.js → editor.api-D7Q83606.js} +1 -1
  17. package/dist/assets/{freemarker2-DVlPoano.js → freemarker2-DCCiHlV6.js} +1 -1
  18. package/dist/assets/geo-CT-wtvaE.js +1 -0
  19. package/dist/assets/{handlebars-B5FZBAMR.js → handlebars-BCxC8xu1.js} +1 -1
  20. package/dist/assets/{html-FodD8KbR.js → html-COwepzSd.js} +1 -1
  21. package/dist/assets/{htmlMode-CsG57VCD.js → htmlMode-Dpg7H0rx.js} +1 -1
  22. package/dist/assets/{image-B3F-nq25.js → image-CMfMMxlC.js} +3 -3
  23. package/dist/assets/index-De1cIZ9N.js +212 -0
  24. package/dist/assets/{index-Y2ock1cw.css → index-ilgbOu6G.css} +1 -1
  25. package/dist/assets/{javascript-B3gonF_H.js → javascript-CSwWxfLr.js} +1 -1
  26. package/dist/assets/{jsonMode-Y3w1ahZg.js → jsonMode-BXRriTpj.js} +1 -1
  27. package/dist/assets/{liquid-CTpWXIYO.js → liquid-Cmkfyogc.js} +1 -1
  28. package/dist/assets/{lspLanguageFeatures-CODBdKEZ.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
  29. package/dist/assets/{mdx-CDAQJZIz.js → mdx-BzrNjNvK.js} +1 -1
  30. package/dist/assets/nodeTypeUtils-BDJsc2Ks.js +1 -0
  31. package/dist/assets/pop-BdKEHslJ.js +1 -0
  32. package/dist/assets/{python-LOIlxJU-.js → python-D4E9jbx7.js} +1 -1
  33. package/dist/assets/{razor-CllPQSHo.js → razor-B3x9cIEM.js} +1 -1
  34. package/dist/assets/{tsMode-CO28ut8t.js → tsMode-dhQYPNMZ.js} +1 -1
  35. package/dist/assets/{typescript-ruU9wKQ_.js → typescript-CITkDxAE.js} +1 -1
  36. package/dist/assets/{workers-Bc1Ywp4-.js → workers-BQvF9bbO.js} +1 -1
  37. package/dist/assets/{xml-DoXP2Dh1.js → xml-DvHlU3A8.js} +1 -1
  38. package/dist/assets/{yaml-D9YPyGiJ.js → yaml-uznsLgs8.js} +1 -1
  39. package/dist/cli/index.js +16227 -12672
  40. package/dist/cli/runtime/io.ts +31 -25
  41. package/dist/contracts/definition.d.ts +21 -2
  42. package/dist/contracts/index.d.ts +2 -0
  43. package/dist/contracts/index.js +2 -0
  44. package/dist/contracts/particles.d.ts +54 -0
  45. package/dist/contracts/particles.js +167 -0
  46. package/dist/contracts/scene.d.ts +129 -0
  47. package/dist/contracts/scene.js +73 -0
  48. package/dist/contracts/schema.js +1 -1
  49. package/dist/contracts/validate.js +4 -2
  50. package/dist/contracts/values.d.ts +3 -2
  51. package/dist/contracts/values.js +9 -0
  52. package/dist/index.html +3 -3
  53. package/dist/io.d.ts +4 -16
  54. package/dist/io.js +26 -8
  55. package/dist/player/app.js +810 -0
  56. package/dist/player/embed.d.ts +4 -0
  57. package/dist/player/embed.js +77 -0
  58. package/dist/player/types.d.ts +35 -0
  59. package/dist/runtime/builtins/core/index.d.ts +3 -2
  60. package/dist/runtime/builtins/core/index.js +6 -2
  61. package/dist/runtime/builtins/core/structural.d.ts +168 -0
  62. package/dist/runtime/builtins/core/structural.js +175 -0
  63. package/dist/runtime/builtins/core/time.d.ts +165 -0
  64. package/dist/runtime/builtins/core/time.js +110 -0
  65. package/dist/runtime/builtins/geo/definitions.d.ts +247 -0
  66. package/dist/runtime/builtins/geo/definitions.js +70 -0
  67. package/dist/runtime/builtins/geo/index.d.ts +2 -1
  68. package/dist/runtime/builtins/geo/index.js +3 -1
  69. package/dist/runtime/builtins/geo/render.d.ts +87 -0
  70. package/dist/runtime/builtins/geo/render.js +87 -0
  71. package/dist/runtime/builtins/geo/svgExport.d.ts +11 -0
  72. package/dist/runtime/builtins/geo/svgExport.js +10 -0
  73. package/dist/runtime/builtins/index.d.ts +5 -2
  74. package/dist/runtime/builtins/index.js +13 -3
  75. package/dist/runtime/builtins/pop/definitions.d.ts +2120 -0
  76. package/dist/runtime/builtins/pop/definitions.js +815 -0
  77. package/dist/runtime/builtins/pop/index.d.ts +2 -0
  78. package/dist/runtime/builtins/pop/index.js +2 -0
  79. package/dist/runtime/builtins/scene/definitions.d.ts +356 -0
  80. package/dist/runtime/builtins/scene/definitions.js +224 -0
  81. package/dist/runtime/builtins/scene/index.d.ts +2 -0
  82. package/dist/runtime/builtins/scene/index.js +2 -0
  83. package/dist/runtime/geometry/render.d.ts +109 -0
  84. package/dist/runtime/geometry/render.js +145 -0
  85. package/dist/runtime/geometry/svg.d.ts +29 -0
  86. package/dist/runtime/geometry/svg.js +11 -5
  87. package/dist/runtime/index.d.ts +1 -0
  88. package/dist/runtime/index.js +1 -0
  89. package/dist/runtime/pop/cache.d.ts +27 -0
  90. package/dist/runtime/pop/cache.js +123 -0
  91. package/dist/runtime/pop/forces.d.ts +202 -0
  92. package/dist/runtime/pop/forces.js +519 -0
  93. package/dist/runtime/pop/index.d.ts +5 -0
  94. package/dist/runtime/pop/index.js +5 -0
  95. package/dist/runtime/pop/state.d.ts +136 -0
  96. package/dist/runtime/pop/state.js +283 -0
  97. package/dist/runtime/pop/trail.d.ts +62 -0
  98. package/dist/runtime/pop/trail.js +142 -0
  99. package/dist/runtime/runtime.js +221 -3
  100. package/dist/runtime/scene/index.d.ts +50 -0
  101. package/dist/runtime/scene/index.js +94 -0
  102. package/dist/studio/panel.d.ts +68 -1
  103. package/doc/HEADLESS_GPU.md +4 -4
  104. package/doc/NODE_REFERENCE.md +372 -1
  105. package/doc/PROJECT_AUTHORING.md +16 -4
  106. package/doc/WEB_PLAYER.md +131 -0
  107. package/package.json +16 -2
  108. package/dist/assets/geo-DhyFdBXX.js +0 -1
  109. package/dist/assets/index-CC6YQw2n.js +0 -212
  110. package/dist/assets/nodeTypeUtils-BBA1JqHx.js +0 -1
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,356 @@
1
+ import { type NodeExecutionContext, type Scene } from "@field/cascade/contracts";
2
+ import type { DefinitionNodeRegistration } from "../../types.js";
3
+ /**
4
+ * The one place the scene built-in namespace is written down.
5
+ *
6
+ * `cascade.scene.*` rather than `cascade.geo.*`, and that is a real distinction
7
+ * rather than tidiness: `cascade.geo` is a *medium* — points, primitives,
8
+ * attributes, operations that take geometry and return geometry. A scene is not
9
+ * geometry. It is an assembly of geometry with a viewpoint and lights, and it is
10
+ * the thing you look at rather than the thing you make.
11
+ *
12
+ * The counter-argument, and it is the one this codebase has already accepted
13
+ * once: a namespace with two nodes in it is a promise rather than a structure.
14
+ * `PLAN viewport` says exactly that about `cascade.render.*`, and prefers
15
+ * `cascade.geo.Render` for the render node on those grounds. The difference is
16
+ * that this namespace has a **type** behind it — `scene`, in the core type list
17
+ * — and a type is the thing a namespace is allowed to be named after.
18
+ */
19
+ export declare const SCENE_NAMESPACE = "cascade.scene";
20
+ export declare function sceneModuleId(name: string): string;
21
+ /**
22
+ * `cascade.scene.Scene` — geometry, a camera and lights, assembled.
23
+ *
24
+ * Marcus's design, 2026-09-09: *"a 'scene' root node with a variadic input …
25
+ * a 'scene' node with a camera, lights and geometry attached becomes a '3d
26
+ * scene'."*
27
+ *
28
+ * ## Why one variadic `any` input rather than three typed ones
29
+ *
30
+ * Three inputs — a variadic `geometry`, a single `camera`, a variadic `light` —
31
+ * would be better typed, and the connection rules would then refuse a wrong
32
+ * wire before the node ever ran. That is a real loss and it is taken
33
+ * deliberately, because the gesture Marcus described is *attach things to it*,
34
+ * and a single port is the difference between dropping a camera on the node and
35
+ * hunting for which of three stacked ports accepts it. Houdini's own `/obj`
36
+ * network works the same way: everything in it is in the scene by being there,
37
+ * not by being wired to the right socket.
38
+ *
39
+ * What that costs is paid back at execute time rather than ignored. Every input
40
+ * is classified, and **anything unclassifiable throws** naming the port and the
41
+ * value's shape — the alternative is a scene that silently omits what you
42
+ * attached, which is the failure mode this codebase keeps writing rules about.
43
+ * Two cameras throw for the same reason: picking one is a guess, and a guess
44
+ * about which way the camera is pointing is not recoverable by looking at the
45
+ * result.
46
+ *
47
+ * A nested scene is flattened. That makes `Scene` associative — assembling two
48
+ * sub-scenes gives the same result as assembling their contents — and it is
49
+ * what lets a big graph build a scene in parts.
50
+ */
51
+ export declare const sceneDefinition: {
52
+ readonly apiVersion: 1;
53
+ readonly label: "Scene";
54
+ readonly description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.";
55
+ readonly icon: "Box";
56
+ readonly runsOn: "portable";
57
+ readonly inputs: {
58
+ readonly inputs: {
59
+ readonly kind: "data";
60
+ readonly type: "any";
61
+ readonly variadic: true;
62
+ readonly description: "Geometry, one camera, and lights, in any order. Scenes are flattened.";
63
+ };
64
+ };
65
+ readonly outputs: {
66
+ readonly scene: {
67
+ readonly kind: "data";
68
+ readonly type: "scene";
69
+ };
70
+ };
71
+ readonly props: {};
72
+ };
73
+ export declare function executeScene(context: NodeExecutionContext<typeof sceneDefinition>): void;
74
+ /**
75
+ * `cascade.scene.Light` — Houdini's `/obj/hlight`, as a node.
76
+ *
77
+ * **Nothing shades with these yet**, and that is stated in the description so
78
+ * that a light which appears to do nothing is understood rather than reported
79
+ * as broken. Every piece in the vault today is lines — `cloud-volumes` is a
80
+ * volume, `particle-type` is trails, `cloud-plots` is marks — so there is no
81
+ * surface to light, and inventing a shading model before something needs one is
82
+ * how you get a shading model nobody wants.
83
+ *
84
+ * It exists now because `Scene` accepts lights now. A type that gains a field
85
+ * later invalidates every serialized scene and every node that builds one; the
86
+ * node is the cheap half of a decision already taken in the type.
87
+ */
88
+ export declare const lightDefinition: {
89
+ readonly apiVersion: 1;
90
+ readonly label: "Light";
91
+ readonly description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.";
92
+ readonly icon: "Lightbulb";
93
+ readonly runsOn: "portable";
94
+ readonly inputs: {
95
+ readonly translate: {
96
+ readonly kind: "data";
97
+ readonly type: "vec3";
98
+ readonly default: readonly [0, 0, 5];
99
+ readonly description: "World position. Ignored for a distant or ambient light.";
100
+ };
101
+ readonly direction: {
102
+ readonly kind: "data";
103
+ readonly type: "vec3";
104
+ readonly default: readonly [0, 0, -1];
105
+ readonly description: "Direction the light points. Used by distant and spot.";
106
+ };
107
+ readonly color: {
108
+ readonly kind: "data";
109
+ readonly type: "color";
110
+ readonly default: readonly [1, 1, 1, 1];
111
+ readonly description: "Houdini light_color.";
112
+ };
113
+ readonly intensity: {
114
+ readonly kind: "data";
115
+ readonly type: "float";
116
+ readonly default: 1;
117
+ readonly min: 0;
118
+ readonly description: "Houdini light_intensity.";
119
+ };
120
+ };
121
+ readonly outputs: {
122
+ readonly light: {
123
+ readonly kind: "data";
124
+ readonly type: "light";
125
+ };
126
+ };
127
+ readonly props: {
128
+ readonly type: {
129
+ readonly type: "string";
130
+ readonly default: "point";
131
+ readonly label: "Light Type";
132
+ readonly control: "select";
133
+ readonly options: readonly [{
134
+ readonly value: "point";
135
+ readonly label: "Point";
136
+ }, {
137
+ readonly value: "distant";
138
+ readonly label: "Distant - directional";
139
+ }, {
140
+ readonly value: "ambient";
141
+ readonly label: "Ambient";
142
+ }, {
143
+ readonly value: "spot";
144
+ readonly label: "Spot";
145
+ }];
146
+ readonly description: "Houdini light_type. Distant is what other renderers call directional.";
147
+ };
148
+ readonly coneangle: {
149
+ readonly type: "float";
150
+ readonly default: 45;
151
+ readonly min: 0;
152
+ readonly max: 180;
153
+ readonly label: "Cone Angle";
154
+ readonly description: "Degrees. Spot only.";
155
+ };
156
+ };
157
+ };
158
+ export declare function executeLight(context: NodeExecutionContext<typeof lightDefinition>): void;
159
+ export declare const sceneRegistration: {
160
+ kind: "definition-v1";
161
+ moduleId: string;
162
+ definition: {
163
+ readonly apiVersion: 1;
164
+ readonly label: "Scene";
165
+ readonly description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.";
166
+ readonly icon: "Box";
167
+ readonly runsOn: "portable";
168
+ readonly inputs: {
169
+ readonly inputs: {
170
+ readonly kind: "data";
171
+ readonly type: "any";
172
+ readonly variadic: true;
173
+ readonly description: "Geometry, one camera, and lights, in any order. Scenes are flattened.";
174
+ };
175
+ };
176
+ readonly outputs: {
177
+ readonly scene: {
178
+ readonly kind: "data";
179
+ readonly type: "scene";
180
+ };
181
+ };
182
+ readonly props: {};
183
+ };
184
+ loadExecute: () => Promise<typeof executeScene>;
185
+ };
186
+ export declare const lightRegistration: {
187
+ kind: "definition-v1";
188
+ moduleId: string;
189
+ definition: {
190
+ readonly apiVersion: 1;
191
+ readonly label: "Light";
192
+ readonly description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.";
193
+ readonly icon: "Lightbulb";
194
+ readonly runsOn: "portable";
195
+ readonly inputs: {
196
+ readonly translate: {
197
+ readonly kind: "data";
198
+ readonly type: "vec3";
199
+ readonly default: readonly [0, 0, 5];
200
+ readonly description: "World position. Ignored for a distant or ambient light.";
201
+ };
202
+ readonly direction: {
203
+ readonly kind: "data";
204
+ readonly type: "vec3";
205
+ readonly default: readonly [0, 0, -1];
206
+ readonly description: "Direction the light points. Used by distant and spot.";
207
+ };
208
+ readonly color: {
209
+ readonly kind: "data";
210
+ readonly type: "color";
211
+ readonly default: readonly [1, 1, 1, 1];
212
+ readonly description: "Houdini light_color.";
213
+ };
214
+ readonly intensity: {
215
+ readonly kind: "data";
216
+ readonly type: "float";
217
+ readonly default: 1;
218
+ readonly min: 0;
219
+ readonly description: "Houdini light_intensity.";
220
+ };
221
+ };
222
+ readonly outputs: {
223
+ readonly light: {
224
+ readonly kind: "data";
225
+ readonly type: "light";
226
+ };
227
+ };
228
+ readonly props: {
229
+ readonly type: {
230
+ readonly type: "string";
231
+ readonly default: "point";
232
+ readonly label: "Light Type";
233
+ readonly control: "select";
234
+ readonly options: readonly [{
235
+ readonly value: "point";
236
+ readonly label: "Point";
237
+ }, {
238
+ readonly value: "distant";
239
+ readonly label: "Distant - directional";
240
+ }, {
241
+ readonly value: "ambient";
242
+ readonly label: "Ambient";
243
+ }, {
244
+ readonly value: "spot";
245
+ readonly label: "Spot";
246
+ }];
247
+ readonly description: "Houdini light_type. Distant is what other renderers call directional.";
248
+ };
249
+ readonly coneangle: {
250
+ readonly type: "float";
251
+ readonly default: 45;
252
+ readonly min: 0;
253
+ readonly max: 180;
254
+ readonly label: "Cone Angle";
255
+ readonly description: "Degrees. Spot only.";
256
+ };
257
+ };
258
+ };
259
+ loadExecute: () => Promise<typeof executeLight>;
260
+ };
261
+ export declare const sceneNodeRegistrations: readonly DefinitionNodeRegistration[];
262
+ /** `[moduleId, definition]` pairs, for a host building a node palette. The same
263
+ * shape `geoNodeDefinitions` and `popNodeDefinitions` have. */
264
+ export declare const sceneNodeDefinitions: readonly [readonly [string, {
265
+ readonly apiVersion: 1;
266
+ readonly label: "Scene";
267
+ readonly description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.";
268
+ readonly icon: "Box";
269
+ readonly runsOn: "portable";
270
+ readonly inputs: {
271
+ readonly inputs: {
272
+ readonly kind: "data";
273
+ readonly type: "any";
274
+ readonly variadic: true;
275
+ readonly description: "Geometry, one camera, and lights, in any order. Scenes are flattened.";
276
+ };
277
+ };
278
+ readonly outputs: {
279
+ readonly scene: {
280
+ readonly kind: "data";
281
+ readonly type: "scene";
282
+ };
283
+ };
284
+ readonly props: {};
285
+ }], readonly [string, {
286
+ readonly apiVersion: 1;
287
+ readonly label: "Light";
288
+ readonly description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.";
289
+ readonly icon: "Lightbulb";
290
+ readonly runsOn: "portable";
291
+ readonly inputs: {
292
+ readonly translate: {
293
+ readonly kind: "data";
294
+ readonly type: "vec3";
295
+ readonly default: readonly [0, 0, 5];
296
+ readonly description: "World position. Ignored for a distant or ambient light.";
297
+ };
298
+ readonly direction: {
299
+ readonly kind: "data";
300
+ readonly type: "vec3";
301
+ readonly default: readonly [0, 0, -1];
302
+ readonly description: "Direction the light points. Used by distant and spot.";
303
+ };
304
+ readonly color: {
305
+ readonly kind: "data";
306
+ readonly type: "color";
307
+ readonly default: readonly [1, 1, 1, 1];
308
+ readonly description: "Houdini light_color.";
309
+ };
310
+ readonly intensity: {
311
+ readonly kind: "data";
312
+ readonly type: "float";
313
+ readonly default: 1;
314
+ readonly min: 0;
315
+ readonly description: "Houdini light_intensity.";
316
+ };
317
+ };
318
+ readonly outputs: {
319
+ readonly light: {
320
+ readonly kind: "data";
321
+ readonly type: "light";
322
+ };
323
+ };
324
+ readonly props: {
325
+ readonly type: {
326
+ readonly type: "string";
327
+ readonly default: "point";
328
+ readonly label: "Light Type";
329
+ readonly control: "select";
330
+ readonly options: readonly [{
331
+ readonly value: "point";
332
+ readonly label: "Point";
333
+ }, {
334
+ readonly value: "distant";
335
+ readonly label: "Distant - directional";
336
+ }, {
337
+ readonly value: "ambient";
338
+ readonly label: "Ambient";
339
+ }, {
340
+ readonly value: "spot";
341
+ readonly label: "Spot";
342
+ }];
343
+ readonly description: "Houdini light_type. Distant is what other renderers call directional.";
344
+ };
345
+ readonly coneangle: {
346
+ readonly type: "float";
347
+ readonly default: 45;
348
+ readonly min: 0;
349
+ readonly max: 180;
350
+ readonly label: "Cone Angle";
351
+ readonly description: "Degrees. Spot only.";
352
+ };
353
+ };
354
+ }]];
355
+ export type { Scene };
356
+ //# sourceMappingURL=definitions.d.ts.map
@@ -0,0 +1,224 @@
1
+ import { LIGHT_DEFAULTS, isCamera, isLight, isScene, } from "@field/cascade/contracts";
2
+ /**
3
+ * The one place the scene built-in namespace is written down.
4
+ *
5
+ * `cascade.scene.*` rather than `cascade.geo.*`, and that is a real distinction
6
+ * rather than tidiness: `cascade.geo` is a *medium* — points, primitives,
7
+ * attributes, operations that take geometry and return geometry. A scene is not
8
+ * geometry. It is an assembly of geometry with a viewpoint and lights, and it is
9
+ * the thing you look at rather than the thing you make.
10
+ *
11
+ * The counter-argument, and it is the one this codebase has already accepted
12
+ * once: a namespace with two nodes in it is a promise rather than a structure.
13
+ * `PLAN viewport` says exactly that about `cascade.render.*`, and prefers
14
+ * `cascade.geo.Render` for the render node on those grounds. The difference is
15
+ * that this namespace has a **type** behind it — `scene`, in the core type list
16
+ * — and a type is the thing a namespace is allowed to be named after.
17
+ */
18
+ export const SCENE_NAMESPACE = "cascade.scene";
19
+ export function sceneModuleId(name) {
20
+ return `${SCENE_NAMESPACE}.${name}`;
21
+ }
22
+ /**
23
+ * `cascade.scene.Scene` — geometry, a camera and lights, assembled.
24
+ *
25
+ * Marcus's design, 2026-09-09: *"a 'scene' root node with a variadic input …
26
+ * a 'scene' node with a camera, lights and geometry attached becomes a '3d
27
+ * scene'."*
28
+ *
29
+ * ## Why one variadic `any` input rather than three typed ones
30
+ *
31
+ * Three inputs — a variadic `geometry`, a single `camera`, a variadic `light` —
32
+ * would be better typed, and the connection rules would then refuse a wrong
33
+ * wire before the node ever ran. That is a real loss and it is taken
34
+ * deliberately, because the gesture Marcus described is *attach things to it*,
35
+ * and a single port is the difference between dropping a camera on the node and
36
+ * hunting for which of three stacked ports accepts it. Houdini's own `/obj`
37
+ * network works the same way: everything in it is in the scene by being there,
38
+ * not by being wired to the right socket.
39
+ *
40
+ * What that costs is paid back at execute time rather than ignored. Every input
41
+ * is classified, and **anything unclassifiable throws** naming the port and the
42
+ * value's shape — the alternative is a scene that silently omits what you
43
+ * attached, which is the failure mode this codebase keeps writing rules about.
44
+ * Two cameras throw for the same reason: picking one is a guess, and a guess
45
+ * about which way the camera is pointing is not recoverable by looking at the
46
+ * result.
47
+ *
48
+ * A nested scene is flattened. That makes `Scene` associative — assembling two
49
+ * sub-scenes gives the same result as assembling their contents — and it is
50
+ * what lets a big graph build a scene in parts.
51
+ */
52
+ export const sceneDefinition = {
53
+ apiVersion: 1,
54
+ label: "Scene",
55
+ description: "Geometry, a camera and lights, assembled into a scene. Attach anything to the input.",
56
+ icon: "Box",
57
+ runsOn: "portable",
58
+ inputs: {
59
+ inputs: {
60
+ kind: "data",
61
+ type: "any",
62
+ variadic: true,
63
+ description: "Geometry, one camera, and lights, in any order. Scenes are flattened.",
64
+ },
65
+ },
66
+ outputs: {
67
+ scene: { kind: "data", type: "scene" },
68
+ },
69
+ props: {},
70
+ };
71
+ export function executeScene(context) {
72
+ const geometry = [];
73
+ const lights = [];
74
+ let camera = null;
75
+ let cameraFrom = -1;
76
+ context.inputs.inputs.forEach((value, index) => {
77
+ if (value === undefined || value === null)
78
+ return;
79
+ if (isScene(value)) {
80
+ geometry.push(...value.geometry);
81
+ lights.push(...value.lights);
82
+ if (value.camera !== null) {
83
+ if (camera !== null)
84
+ throw new Error(`cascade.scene.Scene has two cameras, on inputs ${cameraFrom} and ${index}. A scene has one viewpoint; remove one or use a Switch to choose between them.`);
85
+ camera = value.camera;
86
+ cameraFrom = index;
87
+ }
88
+ return;
89
+ }
90
+ if (isCamera(value)) {
91
+ if (camera !== null)
92
+ throw new Error(`cascade.scene.Scene has two cameras, on inputs ${cameraFrom} and ${index}. A scene has one viewpoint; remove one or use a Switch to choose between them.`);
93
+ camera = value;
94
+ cameraFrom = index;
95
+ return;
96
+ }
97
+ if (isLight(value)) {
98
+ lights.push(value);
99
+ return;
100
+ }
101
+ if (typeof value === "object" && value.kind === "geometry") {
102
+ geometry.push(value);
103
+ return;
104
+ }
105
+ throw new Error(`cascade.scene.Scene input ${index} is not geometry, a camera, a light or a scene (received ${describe(value)}). A scene can only hold those.`);
106
+ });
107
+ context.outputs.scene.set({ geometry, camera, lights });
108
+ }
109
+ function describe(value) {
110
+ if (Array.isArray(value))
111
+ return `an array of ${value.length}`;
112
+ if (typeof value === "object" && value !== null) {
113
+ const kind = value.kind;
114
+ return typeof kind === "string" ? `an object of kind "${kind}"` : "an object";
115
+ }
116
+ return `a ${typeof value}`;
117
+ }
118
+ /**
119
+ * `cascade.scene.Light` — Houdini's `/obj/hlight`, as a node.
120
+ *
121
+ * **Nothing shades with these yet**, and that is stated in the description so
122
+ * that a light which appears to do nothing is understood rather than reported
123
+ * as broken. Every piece in the vault today is lines — `cloud-volumes` is a
124
+ * volume, `particle-type` is trails, `cloud-plots` is marks — so there is no
125
+ * surface to light, and inventing a shading model before something needs one is
126
+ * how you get a shading model nobody wants.
127
+ *
128
+ * It exists now because `Scene` accepts lights now. A type that gains a field
129
+ * later invalidates every serialized scene and every node that builds one; the
130
+ * node is the cheap half of a decision already taken in the type.
131
+ */
132
+ export const lightDefinition = {
133
+ apiVersion: 1,
134
+ label: "Light",
135
+ description: "A light, following Houdini's /obj/hlight. Nothing shades with lights yet — the viewport draws wireframe and points.",
136
+ icon: "Lightbulb",
137
+ runsOn: "portable",
138
+ inputs: {
139
+ translate: {
140
+ kind: "data",
141
+ type: "vec3",
142
+ default: [0, 0, 5],
143
+ description: "World position. Ignored for a distant or ambient light.",
144
+ },
145
+ direction: {
146
+ kind: "data",
147
+ type: "vec3",
148
+ default: [0, 0, -1],
149
+ description: "Direction the light points. Used by distant and spot.",
150
+ },
151
+ color: {
152
+ kind: "data",
153
+ type: "color",
154
+ default: [1, 1, 1, 1],
155
+ description: "Houdini light_color.",
156
+ },
157
+ intensity: {
158
+ kind: "data",
159
+ type: "float",
160
+ default: 1,
161
+ min: 0,
162
+ description: "Houdini light_intensity.",
163
+ },
164
+ },
165
+ outputs: {
166
+ light: { kind: "data", type: "light" },
167
+ },
168
+ props: {
169
+ type: {
170
+ type: "string",
171
+ default: "point",
172
+ label: "Light Type",
173
+ control: "select",
174
+ options: [
175
+ { value: "point", label: "Point" },
176
+ { value: "distant", label: "Distant - directional" },
177
+ { value: "ambient", label: "Ambient" },
178
+ { value: "spot", label: "Spot" },
179
+ ],
180
+ description: "Houdini light_type. Distant is what other renderers call directional.",
181
+ },
182
+ coneangle: {
183
+ type: "float",
184
+ default: 45,
185
+ min: 0,
186
+ max: 180,
187
+ label: "Cone Angle",
188
+ description: "Degrees. Spot only.",
189
+ },
190
+ },
191
+ };
192
+ export function executeLight(context) {
193
+ const type = context.props.type;
194
+ context.outputs.light.set({
195
+ type: type === "distant" || type === "ambient" || type === "spot"
196
+ ? type
197
+ : LIGHT_DEFAULTS.type,
198
+ translate: context.inputs.translate ?? LIGHT_DEFAULTS.translate,
199
+ direction: context.inputs.direction ?? LIGHT_DEFAULTS.direction,
200
+ color: context.inputs.color ?? LIGHT_DEFAULTS.color,
201
+ intensity: context.inputs.intensity ?? LIGHT_DEFAULTS.intensity,
202
+ coneangle: context.props.coneangle,
203
+ });
204
+ }
205
+ export const sceneRegistration = {
206
+ kind: "definition-v1",
207
+ moduleId: sceneModuleId("Scene"),
208
+ definition: sceneDefinition,
209
+ loadExecute: async () => executeScene,
210
+ };
211
+ export const lightRegistration = {
212
+ kind: "definition-v1",
213
+ moduleId: sceneModuleId("Light"),
214
+ definition: lightDefinition,
215
+ loadExecute: async () => executeLight,
216
+ };
217
+ export const sceneNodeRegistrations = Object.freeze([sceneRegistration, lightRegistration]);
218
+ /** `[moduleId, definition]` pairs, for a host building a node palette. The same
219
+ * shape `geoNodeDefinitions` and `popNodeDefinitions` have. */
220
+ export const sceneNodeDefinitions = Object.freeze([
221
+ [sceneModuleId("Scene"), sceneDefinition],
222
+ [sceneModuleId("Light"), lightDefinition],
223
+ ]);
224
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./definitions.js";
2
+ //# sourceMappingURL=index.js.map