@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.
Files changed (129) hide show
  1. package/AGENTS.md +32 -9
  2. package/ARCHITECTURE.md +75 -13
  3. package/CHANGELOG.md +98 -1
  4. package/CONTRIBUTING.md +104 -0
  5. package/DESIGN.md +24 -15
  6. package/README.md +121 -14
  7. package/dist/assets/{CodeEditor-D22fVi-4.js → CodeEditor-dOrU3aQg.js} +7 -7
  8. package/dist/assets/{CodePanel-DPNEYfcR.js → CodePanel-Bqy85qen.js} +2 -2
  9. package/dist/assets/{CustomNodeDialog-DeHCAvgb.js → CustomNodeDialog-DkZlOgjs.js} +1 -1
  10. package/dist/assets/{ProjectSettings-DWpVtWEx.js → ProjectSettings-D192mr6g.js} +2 -2
  11. package/dist/assets/{SettingsDialog-za00IS3L.js → SettingsDialog-BTeeIE3T.js} +1 -1
  12. package/dist/assets/{VersionHistoryDialog-DTMGhI0_.js → VersionHistoryDialog-TZQKkn3Y.js} +1 -1
  13. package/dist/assets/{core-JsyKMO1k.js → core-DwbcGL0o.js} +2 -2
  14. package/dist/assets/{cssMode-emdRheFK.js → cssMode-Dcjn356x.js} +1 -1
  15. package/dist/assets/definitions-C_SY6Bhg.js +1 -0
  16. package/dist/assets/{editor.api-xHX-fMSo.js → editor.api-D7Q83606.js} +1 -1
  17. package/dist/assets/{freemarker2-C3M8_Gs3.js → freemarker2-DCCiHlV6.js} +1 -1
  18. package/dist/assets/geo-CT-wtvaE.js +1 -0
  19. package/dist/assets/{handlebars-CBMurtFk.js → handlebars-BCxC8xu1.js} +1 -1
  20. package/dist/assets/{html-xbLQwknw.js → html-COwepzSd.js} +1 -1
  21. package/dist/assets/{htmlMode-2wKdDTon.js → htmlMode-Dpg7H0rx.js} +1 -1
  22. package/dist/assets/{image-i1j6nWKe.js → image-CMfMMxlC.js} +3 -3
  23. package/dist/assets/index-De1cIZ9N.js +212 -0
  24. package/dist/assets/index-ilgbOu6G.css +1 -0
  25. package/dist/assets/{javascript-DJaOo_2I.js → javascript-CSwWxfLr.js} +1 -1
  26. package/dist/assets/{jsonMode-Bheas8_0.js → jsonMode-BXRriTpj.js} +1 -1
  27. package/dist/assets/{liquid-BCj_BGbM.js → liquid-Cmkfyogc.js} +1 -1
  28. package/dist/assets/{lspLanguageFeatures-BfIsx_xz.js → lspLanguageFeatures-xH9JbZGO.js} +1 -1
  29. package/dist/assets/{mdx-DwiQDiVO.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-BjrB5nqj.js → python-D4E9jbx7.js} +1 -1
  33. package/dist/assets/{razor-CfoBmGDs.js → razor-B3x9cIEM.js} +1 -1
  34. package/dist/assets/{tsMode-BcEoMSxp.js → tsMode-dhQYPNMZ.js} +1 -1
  35. package/dist/assets/{typescript-DIg7TEmQ.js → typescript-CITkDxAE.js} +1 -1
  36. package/dist/assets/{workers-vXYuwtPw.js → workers-BQvF9bbO.js} +1 -1
  37. package/dist/assets/{xml-DexLQIQy.js → xml-DvHlU3A8.js} +1 -1
  38. package/dist/assets/{yaml-DUnD_1dE.js → yaml-uznsLgs8.js} +1 -1
  39. package/dist/cli/index.js +16465 -12096
  40. package/dist/cli/runtime/gpu.ts +71 -0
  41. package/dist/cli/runtime/io.ts +31 -25
  42. package/dist/contracts/camera.d.ts +70 -0
  43. package/dist/contracts/camera.js +27 -0
  44. package/dist/contracts/capabilities.d.ts +6 -6
  45. package/dist/contracts/definition.d.ts +62 -4
  46. package/dist/contracts/index.d.ts +4 -0
  47. package/dist/contracts/index.js +4 -0
  48. package/dist/contracts/particles.d.ts +54 -0
  49. package/dist/contracts/particles.js +167 -0
  50. package/dist/contracts/scene.d.ts +129 -0
  51. package/dist/contracts/scene.js +73 -0
  52. package/dist/contracts/schema.js +26 -3
  53. package/dist/contracts/series.d.ts +75 -0
  54. package/dist/contracts/series.js +2 -0
  55. package/dist/contracts/validate.js +23 -5
  56. package/dist/contracts/values.d.ts +4 -2
  57. package/dist/contracts/values.js +10 -0
  58. package/dist/gpu.d.ts +17 -0
  59. package/dist/gpu.js +61 -1
  60. package/dist/index.html +3 -3
  61. package/dist/io.d.ts +4 -16
  62. package/dist/io.js +26 -8
  63. package/dist/player/app.js +810 -0
  64. package/dist/player/embed.d.ts +4 -0
  65. package/dist/player/embed.js +77 -0
  66. package/dist/player/types.d.ts +35 -0
  67. package/dist/runtime/builtins/core/camera.d.ts +252 -0
  68. package/dist/runtime/builtins/core/camera.js +149 -0
  69. package/dist/runtime/builtins/core/index.d.ts +4 -2
  70. package/dist/runtime/builtins/core/index.js +8 -2
  71. package/dist/runtime/builtins/core/structural.d.ts +168 -0
  72. package/dist/runtime/builtins/core/structural.js +175 -0
  73. package/dist/runtime/builtins/core/time.d.ts +165 -0
  74. package/dist/runtime/builtins/core/time.js +110 -0
  75. package/dist/runtime/builtins/geo/definitions.d.ts +247 -0
  76. package/dist/runtime/builtins/geo/definitions.js +70 -0
  77. package/dist/runtime/builtins/geo/index.d.ts +2 -1
  78. package/dist/runtime/builtins/geo/index.js +3 -1
  79. package/dist/runtime/builtins/geo/render.d.ts +87 -0
  80. package/dist/runtime/builtins/geo/render.js +87 -0
  81. package/dist/runtime/builtins/geo/svgExport.d.ts +11 -0
  82. package/dist/runtime/builtins/geo/svgExport.js +10 -0
  83. package/dist/runtime/builtins/index.d.ts +5 -2
  84. package/dist/runtime/builtins/index.js +13 -3
  85. package/dist/runtime/builtins/pop/definitions.d.ts +2120 -0
  86. package/dist/runtime/builtins/pop/definitions.js +815 -0
  87. package/dist/runtime/builtins/pop/index.d.ts +2 -0
  88. package/dist/runtime/builtins/pop/index.js +2 -0
  89. package/dist/runtime/builtins/scene/definitions.d.ts +356 -0
  90. package/dist/runtime/builtins/scene/definitions.js +224 -0
  91. package/dist/runtime/builtins/scene/index.d.ts +2 -0
  92. package/dist/runtime/builtins/scene/index.js +2 -0
  93. package/dist/runtime/camera/index.d.ts +60 -0
  94. package/dist/runtime/camera/index.js +234 -0
  95. package/dist/runtime/definition/architecture.js +36 -1
  96. package/dist/runtime/geometry/render.d.ts +109 -0
  97. package/dist/runtime/geometry/render.js +145 -0
  98. package/dist/runtime/geometry/svg.d.ts +29 -0
  99. package/dist/runtime/geometry/svg.js +11 -5
  100. package/dist/runtime/gpu.d.ts +33 -0
  101. package/dist/runtime/gpu.js +218 -0
  102. package/dist/runtime/index.d.ts +4 -0
  103. package/dist/runtime/index.js +4 -0
  104. package/dist/runtime/pop/cache.d.ts +27 -0
  105. package/dist/runtime/pop/cache.js +123 -0
  106. package/dist/runtime/pop/forces.d.ts +202 -0
  107. package/dist/runtime/pop/forces.js +519 -0
  108. package/dist/runtime/pop/index.d.ts +5 -0
  109. package/dist/runtime/pop/index.js +5 -0
  110. package/dist/runtime/pop/state.d.ts +136 -0
  111. package/dist/runtime/pop/state.js +283 -0
  112. package/dist/runtime/pop/trail.d.ts +62 -0
  113. package/dist/runtime/pop/trail.js +142 -0
  114. package/dist/runtime/runtime.js +284 -9
  115. package/dist/runtime/scene/index.d.ts +50 -0
  116. package/dist/runtime/scene/index.js +94 -0
  117. package/dist/runtime/series/index.d.ts +41 -0
  118. package/dist/runtime/series/index.js +167 -0
  119. package/dist/runtime/types.d.ts +1 -1
  120. package/dist/studio/panel.d.ts +68 -1
  121. package/doc/HEADLESS_GPU.md +160 -0
  122. package/doc/NODE_REFERENCE.md +408 -1
  123. package/doc/PROJECT_AUTHORING.md +118 -20
  124. package/doc/WEB_PLAYER.md +131 -0
  125. package/package.json +25 -3
  126. package/dist/assets/geo-BAR6lQp5.js +0 -1
  127. package/dist/assets/index-BDV5xGBZ.css +0 -1
  128. package/dist/assets/index-CMcH8AgP.js +0 -212
  129. package/dist/assets/nodeTypeUtils-BBA1JqHx.js +0 -1
@@ -0,0 +1,4 @@
1
+ import type { MountPlayerOptions, PlayerController } from './types.js';
2
+ export type { MountPlayerOptions, PlayerController, PlayerEvent, PlayerListener, PlayerOutputSelection, } from './types.js';
3
+ /** Mount a built player in a realm-isolated same-origin iframe. */
4
+ export declare function mountPlayer(container: HTMLElement, options: MountPlayerOptions): Promise<PlayerController>;
@@ -0,0 +1,77 @@
1
+ // src/player/embed.ts
2
+ async function mountPlayer(container, options) {
3
+ const hostDocument = container.ownerDocument;
4
+ const hostWindow = hostDocument.defaultView;
5
+ if (!hostWindow) throw new Error("Cascade player mount requires a browser window");
6
+ const url = new URL(String(options.url), hostDocument.baseURI);
7
+ if (url.origin !== hostWindow.location.origin) {
8
+ throw new Error(
9
+ "Programmatic Cascade player mounts must be same-origin; use ordinary iframe markup for cross-origin embeds"
10
+ );
11
+ }
12
+ setQueryOption(url, "frame", options.frame ?? 1);
13
+ setQueryOption(url, "fps", options.fps ?? 30);
14
+ url.searchParams.set("autoplay", options.autoplay ? "1" : "0");
15
+ if (options.output) {
16
+ url.searchParams.set("outputNode", options.output.nodeId);
17
+ url.searchParams.set("outputPort", options.output.port);
18
+ } else {
19
+ url.searchParams.delete("outputNode");
20
+ url.searchParams.delete("outputPort");
21
+ }
22
+ const iframe = hostDocument.createElement("iframe");
23
+ iframe.src = url.href;
24
+ iframe.title = "Cascade player";
25
+ iframe.loading = "eager";
26
+ Object.assign(iframe.style, { display: "block", width: "100%", height: "100%", border: "0" });
27
+ iframe.setAttribute("allow", "fullscreen");
28
+ container.append(iframe);
29
+ try {
30
+ await iframeLoaded(iframe);
31
+ const child = iframe.contentWindow;
32
+ const ready = child?.cascadePlayerReady;
33
+ if (!ready) throw new Error("Cascade player did not expose cascadePlayerReady");
34
+ const controller = await ready;
35
+ let disposed = false;
36
+ return {
37
+ play: () => controller.play(),
38
+ pause: () => controller.pause(),
39
+ seek: (frame) => controller.seek(frame),
40
+ setInput: (nodeId, name, value) => controller.setInput(nodeId, name, value),
41
+ setProp: (nodeId, name, value) => controller.setProp(nodeId, name, value),
42
+ getOutput: (nodeId, port) => controller.getOutput(nodeId, port),
43
+ selectOutput: (nodeId, port) => controller.selectOutput(nodeId, port),
44
+ downloadOutput: () => controller.downloadOutput(),
45
+ resize: (width, height) => controller.resize(width, height),
46
+ subscribe: (listener) => controller.subscribe(listener),
47
+ async dispose() {
48
+ if (disposed) return;
49
+ disposed = true;
50
+ try {
51
+ await controller.dispose();
52
+ } finally {
53
+ iframe.remove();
54
+ }
55
+ }
56
+ };
57
+ } catch (error) {
58
+ iframe.remove();
59
+ throw error;
60
+ }
61
+ }
62
+ function setQueryOption(url, name, value) {
63
+ if (value === void 0) return;
64
+ if (!Number.isFinite(value)) throw new TypeError(`${name} must be finite`);
65
+ url.searchParams.set(name, String(value));
66
+ }
67
+ function iframeLoaded(iframe) {
68
+ return new Promise((resolve, reject) => {
69
+ iframe.addEventListener("load", () => resolve(), { once: true });
70
+ iframe.addEventListener("error", () => reject(new Error(`Could not load Cascade player ${iframe.src}`)), {
71
+ once: true
72
+ });
73
+ });
74
+ }
75
+ export {
76
+ mountPlayer
77
+ };
@@ -0,0 +1,35 @@
1
+ export interface PlayerOutputSelection {
2
+ readonly nodeId: string;
3
+ readonly port: string;
4
+ }
5
+ export type PlayerEvent = {
6
+ readonly type: 'frame';
7
+ readonly frame: number;
8
+ } | {
9
+ readonly type: 'playing';
10
+ readonly playing: boolean;
11
+ } | {
12
+ readonly type: 'error';
13
+ readonly error: Error;
14
+ };
15
+ export type PlayerListener = (event: PlayerEvent) => void;
16
+ export interface PlayerController {
17
+ play(): void;
18
+ pause(): void;
19
+ seek(frame: number): Promise<void>;
20
+ setInput(nodeId: string, name: string, value: unknown): Promise<void>;
21
+ setProp(nodeId: string, name: string, value: unknown): Promise<void>;
22
+ getOutput(nodeId: string, port: string): unknown;
23
+ selectOutput(nodeId: string, port: string): Promise<void>;
24
+ downloadOutput(): Promise<Blob>;
25
+ resize(width: number, height: number): void;
26
+ subscribe(listener: PlayerListener): () => void;
27
+ dispose(): Promise<void>;
28
+ }
29
+ export interface MountPlayerOptions {
30
+ readonly url: string | URL;
31
+ readonly frame?: number;
32
+ readonly fps?: number;
33
+ readonly autoplay?: boolean;
34
+ readonly output?: PlayerOutputSelection;
35
+ }
@@ -0,0 +1,252 @@
1
+ import { type NodeExecutionContext } from "@field/cascade/contracts";
2
+ /**
3
+ * `cascade.core.Camera` — Houdini's `/obj/cam`, as a node.
4
+ *
5
+ * Marcus, 2026-09-08: *"I think it just requires a camera. camera should be a
6
+ * core node."* So a renderer takes a camera on a port rather than carrying its
7
+ * own copy of the same eight parameters, which is what `volume-render` was
8
+ * doing.
9
+ *
10
+ * The split between inputs and props is the usual one and it matters here:
11
+ * **what a camera move animates is an input** — position, orientation, the
12
+ * look-at target, the lens — and **what defines the frame is a prop**, because
13
+ * clipping planes, resolution and projection are set once and left. `focal` is
14
+ * an input for exactly this reason: a focus pull is a camera move.
15
+ */
16
+ export declare const cameraDefinition: {
17
+ readonly apiVersion: 1;
18
+ readonly label: "Camera";
19
+ readonly description: "A camera, following Houdini's /obj/cam.";
20
+ readonly icon: "Video";
21
+ readonly runsOn: "portable";
22
+ readonly inputs: {
23
+ readonly translate: {
24
+ readonly kind: "data";
25
+ readonly type: "vec3";
26
+ readonly default: readonly [0, 0, 5];
27
+ readonly description: "World position.";
28
+ };
29
+ readonly rotate: {
30
+ readonly kind: "data";
31
+ readonly type: "vec3";
32
+ readonly default: readonly [0, 0, 0];
33
+ readonly description: "Degrees, XYZ order. The camera looks down its own -Z.";
34
+ };
35
+ readonly focal: {
36
+ readonly kind: "data";
37
+ readonly type: "float";
38
+ readonly default: 50;
39
+ readonly min: 1;
40
+ readonly max: 300;
41
+ readonly description: "Focal length in millimetres.";
42
+ };
43
+ readonly lookat: {
44
+ readonly kind: "data";
45
+ readonly type: "vec3";
46
+ readonly default: readonly [0, 0, 0];
47
+ readonly description: "Point to aim at. Used only when Look At is on, and it replaces Rotate.";
48
+ };
49
+ readonly up: {
50
+ readonly kind: "data";
51
+ readonly type: "vec3";
52
+ readonly default: readonly [0, 1, 0];
53
+ readonly description: "Up vector for Look At.";
54
+ };
55
+ readonly spin: {
56
+ readonly kind: "data";
57
+ readonly type: "float";
58
+ readonly default: 0;
59
+ readonly description: "Degrees added to the Y rotation, after Look At. For turntables.";
60
+ };
61
+ };
62
+ readonly outputs: {
63
+ readonly camera: {
64
+ readonly kind: "data";
65
+ readonly type: "camera";
66
+ };
67
+ };
68
+ readonly props: {
69
+ readonly lookAt: {
70
+ readonly type: "bool";
71
+ readonly default: false;
72
+ readonly label: "Look At";
73
+ readonly description: "Aim at the Look At point instead of using Rotate.";
74
+ };
75
+ readonly aperture: {
76
+ readonly type: "float";
77
+ readonly default: 41.4214;
78
+ readonly min: 1;
79
+ readonly max: 200;
80
+ readonly label: "Aperture";
81
+ readonly description: "Horizontal aperture in millimetres. With the default focal length this is a 45 degree view.";
82
+ };
83
+ readonly resolution: {
84
+ readonly type: "vec2i";
85
+ readonly default: readonly [1280, 720];
86
+ readonly label: "Resolution";
87
+ readonly description: "Frame size in pixels. The vertical field of view is derived from this.";
88
+ };
89
+ readonly aspect: {
90
+ readonly type: "float";
91
+ readonly default: 1;
92
+ readonly min: 0.1;
93
+ readonly max: 10;
94
+ readonly label: "Pixel Aspect";
95
+ readonly description: "The aspect of one pixel, not of the frame.";
96
+ };
97
+ readonly projection: {
98
+ readonly type: "string";
99
+ readonly default: "perspective";
100
+ readonly label: "Projection";
101
+ readonly control: "select";
102
+ readonly options: readonly [{
103
+ readonly value: "perspective";
104
+ readonly label: "Perspective";
105
+ }, {
106
+ readonly value: "orthographic";
107
+ readonly label: "Orthographic";
108
+ }];
109
+ };
110
+ readonly orthowidth: {
111
+ readonly type: "float";
112
+ readonly default: 2;
113
+ readonly min: 0.01;
114
+ readonly label: "Ortho Width";
115
+ readonly description: "Width of the orthographic frame in world units.";
116
+ };
117
+ readonly near: {
118
+ readonly type: "float";
119
+ readonly default: 0.001;
120
+ readonly min: 0;
121
+ readonly label: "Near Clip";
122
+ };
123
+ readonly far: {
124
+ readonly type: "float";
125
+ readonly default: 10000;
126
+ readonly min: 0;
127
+ readonly label: "Far Clip";
128
+ };
129
+ };
130
+ };
131
+ export declare function executeCamera(context: NodeExecutionContext<typeof cameraDefinition>): void;
132
+ export declare const cameraRegistration: {
133
+ kind: "definition-v1";
134
+ moduleId: string;
135
+ definition: {
136
+ readonly apiVersion: 1;
137
+ readonly label: "Camera";
138
+ readonly description: "A camera, following Houdini's /obj/cam.";
139
+ readonly icon: "Video";
140
+ readonly runsOn: "portable";
141
+ readonly inputs: {
142
+ readonly translate: {
143
+ readonly kind: "data";
144
+ readonly type: "vec3";
145
+ readonly default: readonly [0, 0, 5];
146
+ readonly description: "World position.";
147
+ };
148
+ readonly rotate: {
149
+ readonly kind: "data";
150
+ readonly type: "vec3";
151
+ readonly default: readonly [0, 0, 0];
152
+ readonly description: "Degrees, XYZ order. The camera looks down its own -Z.";
153
+ };
154
+ readonly focal: {
155
+ readonly kind: "data";
156
+ readonly type: "float";
157
+ readonly default: 50;
158
+ readonly min: 1;
159
+ readonly max: 300;
160
+ readonly description: "Focal length in millimetres.";
161
+ };
162
+ readonly lookat: {
163
+ readonly kind: "data";
164
+ readonly type: "vec3";
165
+ readonly default: readonly [0, 0, 0];
166
+ readonly description: "Point to aim at. Used only when Look At is on, and it replaces Rotate.";
167
+ };
168
+ readonly up: {
169
+ readonly kind: "data";
170
+ readonly type: "vec3";
171
+ readonly default: readonly [0, 1, 0];
172
+ readonly description: "Up vector for Look At.";
173
+ };
174
+ readonly spin: {
175
+ readonly kind: "data";
176
+ readonly type: "float";
177
+ readonly default: 0;
178
+ readonly description: "Degrees added to the Y rotation, after Look At. For turntables.";
179
+ };
180
+ };
181
+ readonly outputs: {
182
+ readonly camera: {
183
+ readonly kind: "data";
184
+ readonly type: "camera";
185
+ };
186
+ };
187
+ readonly props: {
188
+ readonly lookAt: {
189
+ readonly type: "bool";
190
+ readonly default: false;
191
+ readonly label: "Look At";
192
+ readonly description: "Aim at the Look At point instead of using Rotate.";
193
+ };
194
+ readonly aperture: {
195
+ readonly type: "float";
196
+ readonly default: 41.4214;
197
+ readonly min: 1;
198
+ readonly max: 200;
199
+ readonly label: "Aperture";
200
+ readonly description: "Horizontal aperture in millimetres. With the default focal length this is a 45 degree view.";
201
+ };
202
+ readonly resolution: {
203
+ readonly type: "vec2i";
204
+ readonly default: readonly [1280, 720];
205
+ readonly label: "Resolution";
206
+ readonly description: "Frame size in pixels. The vertical field of view is derived from this.";
207
+ };
208
+ readonly aspect: {
209
+ readonly type: "float";
210
+ readonly default: 1;
211
+ readonly min: 0.1;
212
+ readonly max: 10;
213
+ readonly label: "Pixel Aspect";
214
+ readonly description: "The aspect of one pixel, not of the frame.";
215
+ };
216
+ readonly projection: {
217
+ readonly type: "string";
218
+ readonly default: "perspective";
219
+ readonly label: "Projection";
220
+ readonly control: "select";
221
+ readonly options: readonly [{
222
+ readonly value: "perspective";
223
+ readonly label: "Perspective";
224
+ }, {
225
+ readonly value: "orthographic";
226
+ readonly label: "Orthographic";
227
+ }];
228
+ };
229
+ readonly orthowidth: {
230
+ readonly type: "float";
231
+ readonly default: 2;
232
+ readonly min: 0.01;
233
+ readonly label: "Ortho Width";
234
+ readonly description: "Width of the orthographic frame in world units.";
235
+ };
236
+ readonly near: {
237
+ readonly type: "float";
238
+ readonly default: 0.001;
239
+ readonly min: 0;
240
+ readonly label: "Near Clip";
241
+ };
242
+ readonly far: {
243
+ readonly type: "float";
244
+ readonly default: 10000;
245
+ readonly min: 0;
246
+ readonly label: "Far Clip";
247
+ };
248
+ };
249
+ };
250
+ loadExecute: () => Promise<typeof executeCamera>;
251
+ };
252
+ //# sourceMappingURL=camera.d.ts.map
@@ -0,0 +1,149 @@
1
+ import { CAMERA_DEFAULTS } from "@field/cascade/contracts";
2
+ import { lookAtRotation } from "../../camera/index.js";
3
+ /**
4
+ * `cascade.core.Camera` — Houdini's `/obj/cam`, as a node.
5
+ *
6
+ * Marcus, 2026-09-08: *"I think it just requires a camera. camera should be a
7
+ * core node."* So a renderer takes a camera on a port rather than carrying its
8
+ * own copy of the same eight parameters, which is what `volume-render` was
9
+ * doing.
10
+ *
11
+ * The split between inputs and props is the usual one and it matters here:
12
+ * **what a camera move animates is an input** — position, orientation, the
13
+ * look-at target, the lens — and **what defines the frame is a prop**, because
14
+ * clipping planes, resolution and projection are set once and left. `focal` is
15
+ * an input for exactly this reason: a focus pull is a camera move.
16
+ */
17
+ export const cameraDefinition = {
18
+ apiVersion: 1,
19
+ label: "Camera",
20
+ description: "A camera, following Houdini's /obj/cam.",
21
+ icon: "Video",
22
+ runsOn: "portable",
23
+ inputs: {
24
+ translate: {
25
+ kind: "data",
26
+ type: "vec3",
27
+ default: [0, 0, 5],
28
+ description: "World position.",
29
+ },
30
+ rotate: {
31
+ kind: "data",
32
+ type: "vec3",
33
+ default: [0, 0, 0],
34
+ description: "Degrees, XYZ order. The camera looks down its own -Z.",
35
+ },
36
+ focal: {
37
+ kind: "data",
38
+ type: "float",
39
+ default: 50,
40
+ min: 1,
41
+ max: 300,
42
+ description: "Focal length in millimetres.",
43
+ },
44
+ lookat: {
45
+ kind: "data",
46
+ type: "vec3",
47
+ default: [0, 0, 0],
48
+ description: "Point to aim at. Used only when Look At is on, and it replaces Rotate.",
49
+ },
50
+ up: {
51
+ kind: "data",
52
+ type: "vec3",
53
+ default: [0, 1, 0],
54
+ description: "Up vector for Look At.",
55
+ },
56
+ spin: {
57
+ kind: "data",
58
+ type: "float",
59
+ default: 0,
60
+ description: "Degrees added to the Y rotation, after Look At. For turntables.",
61
+ },
62
+ },
63
+ outputs: {
64
+ camera: { kind: "data", type: "camera" },
65
+ },
66
+ props: {
67
+ lookAt: {
68
+ type: "bool",
69
+ default: false,
70
+ label: "Look At",
71
+ description: "Aim at the Look At point instead of using Rotate.",
72
+ },
73
+ aperture: {
74
+ type: "float",
75
+ default: 41.4214,
76
+ min: 1,
77
+ max: 200,
78
+ label: "Aperture",
79
+ description: "Horizontal aperture in millimetres. With the default focal length this is a 45 degree view.",
80
+ },
81
+ resolution: {
82
+ type: "vec2i",
83
+ default: [1280, 720],
84
+ label: "Resolution",
85
+ description: "Frame size in pixels. The vertical field of view is derived from this.",
86
+ },
87
+ aspect: {
88
+ type: "float",
89
+ default: 1,
90
+ min: 0.1,
91
+ max: 10,
92
+ label: "Pixel Aspect",
93
+ description: "The aspect of one pixel, not of the frame.",
94
+ },
95
+ projection: {
96
+ type: "string",
97
+ default: "perspective",
98
+ label: "Projection",
99
+ control: "select",
100
+ options: [
101
+ { value: "perspective", label: "Perspective" },
102
+ { value: "orthographic", label: "Orthographic" },
103
+ ],
104
+ },
105
+ orthowidth: {
106
+ type: "float",
107
+ default: 2,
108
+ min: 0.01,
109
+ label: "Ortho Width",
110
+ description: "Width of the orthographic frame in world units.",
111
+ },
112
+ near: { type: "float", default: 0.001, min: 0, label: "Near Clip" },
113
+ far: { type: "float", default: 10000, min: 0, label: "Far Clip" },
114
+ },
115
+ };
116
+ export function executeCamera(context) {
117
+ const { translate, rotate, focal, lookat, up, spin } = context.inputs;
118
+ const props = context.props;
119
+ const aimed = props.lookAt
120
+ ? lookAtRotation(translate, lookat, up)
121
+ : rotate;
122
+ // Spin is applied after look-at rather than folded into it, so a turntable
123
+ // keeps pointing at its subject while it goes round. Degrees, because
124
+ // `rotate` is degrees and adding a different unit to it would be a trap.
125
+ const oriented = spin === 0
126
+ ? aimed
127
+ : [aimed[0], aimed[1] + spin, aimed[2]];
128
+ const camera = {
129
+ ...CAMERA_DEFAULTS,
130
+ translate: translate,
131
+ rotate: oriented,
132
+ focal,
133
+ aperture: props.aperture,
134
+ resolution: props.resolution,
135
+ aspect: props.aspect,
136
+ projection: props.projection === "orthographic" ? "orthographic" : "perspective",
137
+ orthowidth: props.orthowidth,
138
+ near: props.near,
139
+ far: props.far,
140
+ };
141
+ context.outputs.camera.set(camera);
142
+ }
143
+ export const cameraRegistration = {
144
+ kind: "definition-v1",
145
+ moduleId: "cascade.core.Camera",
146
+ definition: cameraDefinition,
147
+ loadExecute: async () => executeCamera,
148
+ };
149
+ //# sourceMappingURL=camera.js.map
@@ -1,9 +1,11 @@
1
1
  import type { DefinitionNodeRegistration } from "../../types.js";
2
+ import { cameraRegistration } from "./camera.js";
2
3
  import { randomRegistration } from "./random.js";
3
4
  import { remapRegistration } from "./remap.js";
4
5
  import { mergeRegistration, nullRegistration, selectRegistration, switchRegistration } from "./routing.js";
5
- import { inputRegistration, outputRegistration, subnetRegistration } from "./structural.js";
6
- export { inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, };
6
+ import { feedbackRegistration, inputRegistration, outputRegistration, previousRegistration, subnetRegistration } from "./structural.js";
7
+ import { timeRegistration } from "./time.js";
8
+ export { cameraRegistration, feedbackRegistration, previousRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, timeRegistration, };
7
9
  export declare const coreNodeRegistrations: readonly DefinitionNodeRegistration[];
8
10
  export declare function coreNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,10 @@
1
+ import { cameraRegistration } from "./camera.js";
1
2
  import { randomRegistration } from "./random.js";
2
3
  import { remapRegistration } from "./remap.js";
3
4
  import { mergeRegistration, nullRegistration, selectRegistration, switchRegistration } from "./routing.js";
4
- import { inputRegistration, outputRegistration, subnetRegistration } from "./structural.js";
5
- export { inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, };
5
+ import { feedbackRegistration, inputRegistration, outputRegistration, previousRegistration, subnetRegistration, } from "./structural.js";
6
+ import { timeRegistration } from "./time.js";
7
+ export { cameraRegistration, feedbackRegistration, previousRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, timeRegistration, };
6
8
  export const coreNodeRegistrations = Object.freeze([
7
9
  subnetRegistration,
8
10
  inputRegistration,
@@ -13,6 +15,10 @@ export const coreNodeRegistrations = Object.freeze([
13
15
  selectRegistration,
14
16
  randomRegistration,
15
17
  remapRegistration,
18
+ cameraRegistration,
19
+ timeRegistration,
20
+ feedbackRegistration,
21
+ previousRegistration,
16
22
  ]);
17
23
  const registrationsById = new Map(coreNodeRegistrations.map((registration) => [registration.moduleId, registration]));
18
24
  export function coreNodeRegistration(moduleId) {