@field/cascade 0.3.1 → 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 +32 -9
- package/ARCHITECTURE.md +75 -13
- package/CHANGELOG.md +98 -1
- package/CONTRIBUTING.md +104 -0
- package/DESIGN.md +24 -15
- package/README.md +121 -14
- package/dist/assets/{CodeEditor-D22fVi-4.js → CodeEditor-dOrU3aQg.js} +7 -7
- package/dist/assets/{CodePanel-DPNEYfcR.js → CodePanel-Bqy85qen.js} +2 -2
- package/dist/assets/{CustomNodeDialog-DeHCAvgb.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
- package/dist/assets/{ProjectSettings-DWpVtWEx.js → ProjectSettings-D192mr6g.js} +2 -2
- package/dist/assets/{SettingsDialog-za00IS3L.js → SettingsDialog-BTeeIE3T.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DTMGhI0_.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
- package/dist/assets/{core-JsyKMO1k.js → core-DwbcGL0o.js} +2 -2
- package/dist/assets/{cssMode-emdRheFK.js → cssMode-Dcjn356x.js} +1 -1
- package/dist/assets/definitions-C_SY6Bhg.js +1 -0
- package/dist/assets/{editor.api-xHX-fMSo.js → editor.api-D7Q83606.js} +1 -1
- package/dist/assets/{freemarker2-C3M8_Gs3.js → freemarker2-DCCiHlV6.js} +1 -1
- package/dist/assets/geo-CT-wtvaE.js +1 -0
- package/dist/assets/{handlebars-CBMurtFk.js → handlebars-BCxC8xu1.js} +1 -1
- package/dist/assets/{html-xbLQwknw.js → html-COwepzSd.js} +1 -1
- package/dist/assets/{htmlMode-2wKdDTon.js → htmlMode-Dpg7H0rx.js} +1 -1
- package/dist/assets/{image-i1j6nWKe.js → image-CMfMMxlC.js} +3 -3
- package/dist/assets/index-De1cIZ9N.js +212 -0
- package/dist/assets/index-ilgbOu6G.css +1 -0
- package/dist/assets/{javascript-DJaOo_2I.js → javascript-CSwWxfLr.js} +1 -1
- package/dist/assets/{jsonMode-Bheas8_0.js → jsonMode-BXRriTpj.js} +1 -1
- package/dist/assets/{liquid-BCj_BGbM.js → liquid-Cmkfyogc.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BfIsx_xz.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
- package/dist/assets/{mdx-DwiQDiVO.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-BjrB5nqj.js → python-D4E9jbx7.js} +1 -1
- package/dist/assets/{razor-CfoBmGDs.js → razor-B3x9cIEM.js} +1 -1
- package/dist/assets/{tsMode-BcEoMSxp.js → tsMode-dhQYPNMZ.js} +1 -1
- package/dist/assets/{typescript-DIg7TEmQ.js → typescript-CITkDxAE.js} +1 -1
- package/dist/assets/{workers-vXYuwtPw.js → workers-BQvF9bbO.js} +1 -1
- package/dist/assets/{xml-DexLQIQy.js → xml-DvHlU3A8.js} +1 -1
- package/dist/assets/{yaml-DUnD_1dE.js → yaml-uznsLgs8.js} +1 -1
- package/dist/cli/index.js +16465 -12096
- package/dist/cli/runtime/gpu.ts +71 -0
- package/dist/cli/runtime/io.ts +31 -25
- package/dist/contracts/camera.d.ts +70 -0
- package/dist/contracts/camera.js +27 -0
- package/dist/contracts/capabilities.d.ts +6 -6
- package/dist/contracts/definition.d.ts +62 -4
- package/dist/contracts/index.d.ts +4 -0
- package/dist/contracts/index.js +4 -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 +26 -3
- package/dist/contracts/series.d.ts +75 -0
- package/dist/contracts/series.js +2 -0
- package/dist/contracts/validate.js +23 -5
- package/dist/contracts/values.d.ts +4 -2
- package/dist/contracts/values.js +10 -0
- package/dist/gpu.d.ts +17 -0
- package/dist/gpu.js +61 -1
- 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/camera.d.ts +252 -0
- package/dist/runtime/builtins/core/camera.js +149 -0
- package/dist/runtime/builtins/core/index.d.ts +4 -2
- package/dist/runtime/builtins/core/index.js +8 -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/camera/index.d.ts +60 -0
- package/dist/runtime/camera/index.js +234 -0
- package/dist/runtime/definition/architecture.js +36 -1
- 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/gpu.d.ts +33 -0
- package/dist/runtime/gpu.js +218 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +4 -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 +284 -9
- package/dist/runtime/scene/index.d.ts +50 -0
- package/dist/runtime/scene/index.js +94 -0
- package/dist/runtime/series/index.d.ts +41 -0
- package/dist/runtime/series/index.js +167 -0
- package/dist/runtime/types.d.ts +1 -1
- package/dist/studio/panel.d.ts +68 -1
- package/doc/HEADLESS_GPU.md +160 -0
- package/doc/NODE_REFERENCE.md +408 -1
- package/doc/PROJECT_AUTHORING.md +118 -20
- package/doc/WEB_PLAYER.md +131 -0
- package/package.json +25 -3
- package/dist/assets/geo-BAR6lQp5.js +0 -1
- package/dist/assets/index-BDV5xGBZ.css +0 -1
- package/dist/assets/index-CMcH8AgP.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
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Camera, Mat4, Vec3 } from "@field/cascade/contracts";
|
|
2
|
+
/** The camera's own axes in world space. */
|
|
3
|
+
export interface CameraBasis {
|
|
4
|
+
/** Where the camera is. */
|
|
5
|
+
readonly eye: Vec3;
|
|
6
|
+
/** Unit vector the camera looks along — its own -Z. */
|
|
7
|
+
readonly forward: Vec3;
|
|
8
|
+
/** Unit vector to the camera's right — its own +X. */
|
|
9
|
+
readonly right: Vec3;
|
|
10
|
+
/** Unit vector for the camera's up — its own +Y. */
|
|
11
|
+
readonly up: Vec3;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The rotation the camera needs to look at a point, as Houdini's look-at does.
|
|
15
|
+
*
|
|
16
|
+
* Returned as degrees in XYZ order so it can be written straight into
|
|
17
|
+
* `camera.rotate` — which is what makes look-at an override of the transform
|
|
18
|
+
* rather than a second, parallel way to orient a camera.
|
|
19
|
+
*
|
|
20
|
+
* **`up` chooses the roll, and only the roll.** Yaw and pitch are fixed the
|
|
21
|
+
* moment a direction is given; all an up vector can still decide is the
|
|
22
|
+
* rotation about that direction. The default `[0, 1, 0]` is Houdini's own
|
|
23
|
+
* `upvector` default and gives roll zero for any horizontal up, which is why
|
|
24
|
+
* every ordinary orbit never needs to think about it.
|
|
25
|
+
*
|
|
26
|
+
* Written first with `roll = 0` and the parameter ignored, which was wrong in
|
|
27
|
+
* the way this codebase keeps finding: the node exposes `up` as an input, so a
|
|
28
|
+
* value set there did nothing at all and nothing said so.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lookAtRotation(from: Vec3, to: Vec3, up?: Vec3): Vec3;
|
|
31
|
+
/** The camera's axes, applying Houdini's XYZ rotation order to a camera that
|
|
32
|
+
* looks down -Z at rest. */
|
|
33
|
+
export declare function cameraBasis(camera: Camera): CameraBasis;
|
|
34
|
+
/**
|
|
35
|
+
* The horizontal field of view in degrees, from the lens rather than stored.
|
|
36
|
+
*
|
|
37
|
+
* Houdini's defaults of 50 mm and 41.4214 mm are exactly 45°, which is the
|
|
38
|
+
* check worth having in a test: it is the one value that proves the formula and
|
|
39
|
+
* the defaults agree.
|
|
40
|
+
*/
|
|
41
|
+
export declare function horizontalFov(camera: Camera): number;
|
|
42
|
+
/**
|
|
43
|
+
* The VERTICAL field of view in degrees.
|
|
44
|
+
*
|
|
45
|
+
* Houdini does not store a vertical aperture; it derives one from the
|
|
46
|
+
* horizontal aperture, the resolution ratio and the pixel aspect. Which is why
|
|
47
|
+
* `resolution` is on the camera at all — without it there is no vertical angle
|
|
48
|
+
* to be had.
|
|
49
|
+
*/
|
|
50
|
+
export declare function verticalFov(camera: Camera): number;
|
|
51
|
+
/** Frame aspect — the shape of the picture, pixel aspect included. Distinct
|
|
52
|
+
* from `camera.aspect`, which is the aspect of one pixel. */
|
|
53
|
+
export declare function frameAspect(camera: Camera): number;
|
|
54
|
+
/** Column-major, GLSL convention, matching `MATRIX_TYPES` in Studio: element 0
|
|
55
|
+
* starts the first COLUMN. */
|
|
56
|
+
export declare function viewMatrix(camera: Camera): Mat4;
|
|
57
|
+
/** Column-major, and orthographic when the camera says so — `orthowidth` is a
|
|
58
|
+
* width in world units, so the height comes from the frame aspect. */
|
|
59
|
+
export declare function projectionMatrix(camera: Camera): Mat4;
|
|
60
|
+
//# sourceMappingURL=index.d.ts.map
|