@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,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
|