@field/cascade 0.3.1 → 0.4.0
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 +14 -9
- package/ARCHITECTURE.md +22 -6
- package/CHANGELOG.md +51 -1
- package/CONTRIBUTING.md +71 -0
- package/DESIGN.md +19 -13
- package/README.md +42 -11
- package/dist/assets/{CodeEditor-D22fVi-4.js → CodeEditor-BNOJIDzL.js} +4 -4
- package/dist/assets/{CodePanel-DPNEYfcR.js → CodePanel-2wp2G0Fj.js} +2 -2
- package/dist/assets/{CustomNodeDialog-DeHCAvgb.js → CustomNodeDialog-DNqaVBqA.js} +1 -1
- package/dist/assets/{ProjectSettings-DWpVtWEx.js → ProjectSettings-DcdYF2E1.js} +1 -1
- package/dist/assets/{SettingsDialog-za00IS3L.js → SettingsDialog-D2xVvyiR.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DTMGhI0_.js → VersionHistoryDialog-DW288ykF.js} +1 -1
- package/dist/assets/{core-JsyKMO1k.js → core-B1y38i95.js} +1 -1
- package/dist/assets/{cssMode-emdRheFK.js → cssMode-BLucnaln.js} +1 -1
- package/dist/assets/{editor.api-xHX-fMSo.js → editor.api-B3Et3Pph.js} +1 -1
- package/dist/assets/{freemarker2-C3M8_Gs3.js → freemarker2-DVlPoano.js} +1 -1
- package/dist/assets/geo-DhyFdBXX.js +1 -0
- package/dist/assets/{handlebars-CBMurtFk.js → handlebars-B5FZBAMR.js} +1 -1
- package/dist/assets/{html-xbLQwknw.js → html-FodD8KbR.js} +1 -1
- package/dist/assets/{htmlMode-2wKdDTon.js → htmlMode-CsG57VCD.js} +1 -1
- package/dist/assets/{image-i1j6nWKe.js → image-B3F-nq25.js} +1 -1
- package/dist/assets/index-CC6YQw2n.js +212 -0
- package/dist/assets/index-Y2ock1cw.css +1 -0
- package/dist/assets/{javascript-DJaOo_2I.js → javascript-B3gonF_H.js} +1 -1
- package/dist/assets/{jsonMode-Bheas8_0.js → jsonMode-Y3w1ahZg.js} +1 -1
- package/dist/assets/{liquid-BCj_BGbM.js → liquid-CTpWXIYO.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BfIsx_xz.js → lspLanguageFeatures-CODBdKEZ.js} +1 -1
- package/dist/assets/{mdx-DwiQDiVO.js → mdx-CDAQJZIz.js} +1 -1
- package/dist/assets/{python-BjrB5nqj.js → python-LOIlxJU-.js} +1 -1
- package/dist/assets/{razor-CfoBmGDs.js → razor-CllPQSHo.js} +1 -1
- package/dist/assets/{tsMode-BcEoMSxp.js → tsMode-CO28ut8t.js} +1 -1
- package/dist/assets/{typescript-DIg7TEmQ.js → typescript-ruU9wKQ_.js} +1 -1
- package/dist/assets/{workers-vXYuwtPw.js → workers-Bc1Ywp4-.js} +1 -1
- package/dist/assets/{xml-DexLQIQy.js → xml-DoXP2Dh1.js} +1 -1
- package/dist/assets/{yaml-DUnD_1dE.js → yaml-D9YPyGiJ.js} +1 -1
- package/dist/cli/index.js +999 -185
- package/dist/cli/runtime/gpu.ts +71 -0
- 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 +41 -2
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/schema.js +25 -2
- package/dist/contracts/series.d.ts +75 -0
- package/dist/contracts/series.js +2 -0
- package/dist/contracts/validate.js +19 -3
- package/dist/contracts/values.d.ts +3 -2
- package/dist/contracts/values.js +1 -0
- package/dist/gpu.d.ts +17 -0
- package/dist/gpu.js +61 -1
- package/dist/index.html +2 -2
- 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 +2 -1
- package/dist/runtime/builtins/core/index.js +3 -1
- 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/gpu.d.ts +33 -0
- package/dist/runtime/gpu.js +218 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/runtime.js +63 -6
- 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/doc/HEADLESS_GPU.md +160 -0
- package/doc/NODE_REFERENCE.md +36 -0
- package/doc/PROJECT_AUTHORING.md +106 -20
- package/package.json +10 -2
- package/dist/assets/geo-BAR6lQp5.js +0 -1
- package/dist/assets/index-BDV5xGBZ.css +0 -1
- package/dist/assets/index-CMcH8AgP.js +0 -212
|
@@ -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,10 @@
|
|
|
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
6
|
import { inputRegistration, outputRegistration, subnetRegistration } from "./structural.js";
|
|
6
|
-
export { inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, };
|
|
7
|
+
export { cameraRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, };
|
|
7
8
|
export declare const coreNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
8
9
|
export declare function coreNodeRegistration(moduleId: string): DefinitionNodeRegistration | undefined;
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,8 +1,9 @@
|
|
|
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
5
|
import { inputRegistration, outputRegistration, subnetRegistration } from "./structural.js";
|
|
5
|
-
export { inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, };
|
|
6
|
+
export { cameraRegistration, inputRegistration, mergeRegistration, nullRegistration, outputRegistration, randomRegistration, remapRegistration, selectRegistration, subnetRegistration, switchRegistration, };
|
|
6
7
|
export const coreNodeRegistrations = Object.freeze([
|
|
7
8
|
subnetRegistration,
|
|
8
9
|
inputRegistration,
|
|
@@ -13,6 +14,7 @@ export const coreNodeRegistrations = Object.freeze([
|
|
|
13
14
|
selectRegistration,
|
|
14
15
|
randomRegistration,
|
|
15
16
|
remapRegistration,
|
|
17
|
+
cameraRegistration,
|
|
16
18
|
]);
|
|
17
19
|
const registrationsById = new Map(coreNodeRegistrations.map((registration) => [registration.moduleId, registration]));
|
|
18
20
|
export function coreNodeRegistration(moduleId) {
|
|
@@ -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
|