@motionscript/plot 0.0.0-stage → 0.1.0-alpha.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/dist/browser/chunks/chunk-RCFWYW6O.js +2 -0
  4. package/dist/browser/chunks/chunk-RCFWYW6O.js.map +7 -0
  5. package/dist/browser/index.js +84 -0
  6. package/dist/browser/index.js.map +7 -0
  7. package/dist/browser/kit.js +2 -0
  8. package/dist/browser/kit.js.map +7 -0
  9. package/dist/browser/manifest.json +12 -0
  10. package/dist/engine.d.ts +14 -0
  11. package/dist/engine.d.ts.map +1 -0
  12. package/dist/engine.js +14 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/graph2d/curve-cache.d.ts +60 -0
  15. package/dist/graph2d/curve-cache.d.ts.map +1 -0
  16. package/dist/graph2d/curve-cache.js +79 -0
  17. package/dist/graph2d/curve-cache.js.map +1 -0
  18. package/dist/graph2d/curve-error.d.ts +12 -0
  19. package/dist/graph2d/curve-error.d.ts.map +1 -0
  20. package/dist/graph2d/curve-error.js +15 -0
  21. package/dist/graph2d/curve-error.js.map +1 -0
  22. package/dist/graph2d/curve.d.ts +104 -0
  23. package/dist/graph2d/curve.d.ts.map +1 -0
  24. package/dist/graph2d/curve.js +531 -0
  25. package/dist/graph2d/curve.js.map +1 -0
  26. package/dist/graph2d/graph2d.d.ts +164 -0
  27. package/dist/graph2d/graph2d.d.ts.map +1 -0
  28. package/dist/graph2d/graph2d.js +404 -0
  29. package/dist/graph2d/graph2d.js.map +1 -0
  30. package/dist/graph2d/index.d.ts +32 -0
  31. package/dist/graph2d/index.d.ts.map +1 -0
  32. package/dist/graph2d/index.js +32 -0
  33. package/dist/graph2d/index.js.map +1 -0
  34. package/dist/graph2d/plane-fill.d.ts +110 -0
  35. package/dist/graph2d/plane-fill.d.ts.map +1 -0
  36. package/dist/graph2d/plane-fill.js +248 -0
  37. package/dist/graph2d/plane-fill.js.map +1 -0
  38. package/dist/graph2d/plane.d.ts +179 -0
  39. package/dist/graph2d/plane.d.ts.map +1 -0
  40. package/dist/graph2d/plane.js +359 -0
  41. package/dist/graph2d/plane.js.map +1 -0
  42. package/dist/graph2d/shared.d.ts +40 -0
  43. package/dist/graph2d/shared.d.ts.map +1 -0
  44. package/dist/graph2d/shared.js +70 -0
  45. package/dist/graph2d/shared.js.map +1 -0
  46. package/dist/graph3d/expression.d.ts +30 -0
  47. package/dist/graph3d/expression.d.ts.map +1 -0
  48. package/dist/graph3d/expression.js +35 -0
  49. package/dist/graph3d/expression.js.map +1 -0
  50. package/dist/graph3d/graph3d.d.ts +186 -0
  51. package/dist/graph3d/graph3d.d.ts.map +1 -0
  52. package/dist/graph3d/graph3d.js +404 -0
  53. package/dist/graph3d/graph3d.js.map +1 -0
  54. package/dist/graph3d/index.d.ts +22 -0
  55. package/dist/graph3d/index.d.ts.map +1 -0
  56. package/dist/graph3d/index.js +22 -0
  57. package/dist/graph3d/index.js.map +1 -0
  58. package/dist/graph3d/shared.d.ts +61 -0
  59. package/dist/graph3d/shared.d.ts.map +1 -0
  60. package/dist/graph3d/shared.js +101 -0
  61. package/dist/graph3d/shared.js.map +1 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +4 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/kit/equations.d.ts +56 -0
  67. package/dist/kit/equations.d.ts.map +1 -0
  68. package/dist/kit/equations.js +63 -0
  69. package/dist/kit/equations.js.map +1 -0
  70. package/dist/kit/expression.d.ts +60 -0
  71. package/dist/kit/expression.d.ts.map +1 -0
  72. package/dist/kit/expression.js +268 -0
  73. package/dist/kit/expression.js.map +1 -0
  74. package/dist/kit/index.d.ts +15 -0
  75. package/dist/kit/index.d.ts.map +1 -0
  76. package/dist/kit/index.js +13 -0
  77. package/dist/kit/index.js.map +1 -0
  78. package/dist/nodes.d.ts +19 -0
  79. package/dist/nodes.d.ts.map +1 -0
  80. package/dist/nodes.js +19 -0
  81. package/dist/nodes.js.map +1 -0
  82. package/package.json +68 -3
  83. package/registry.json +34 -0
  84. package/src/engine.ts +13 -0
  85. package/src/graph2d/curve-cache.ts +103 -0
  86. package/src/graph2d/curve-error.ts +19 -0
  87. package/src/graph2d/curve.ts +657 -0
  88. package/src/graph2d/graph2d.ts +586 -0
  89. package/src/graph2d/index.ts +31 -0
  90. package/src/graph2d/plane-fill.ts +308 -0
  91. package/src/graph2d/plane.ts +457 -0
  92. package/src/graph2d/shared.ts +103 -0
  93. package/src/graph3d/expression.ts +51 -0
  94. package/src/graph3d/graph3d.ts +614 -0
  95. package/src/graph3d/index.ts +21 -0
  96. package/src/graph3d/shared.ts +143 -0
  97. package/src/index.ts +3 -0
  98. package/src/kit/equations.ts +102 -0
  99. package/src/kit/expression.ts +323 -0
  100. package/src/kit/index.ts +29 -0
  101. package/src/nodes.ts +19 -0
  102. package/README.md +0 -4
@@ -0,0 +1,404 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { command, easeInOut, node, Canvas3D, Fills, Geo, Graphics3D, Mat, Scene3D, lerpNumber, property, } from "@motionscript/core";
8
+ import { orbitProps } from "@motionscript/core/component";
9
+ import { compileExpressionCached } from "./expression.js";
10
+ import { lerpColor3D, lerpCount, lerpEquations, lerpFinite, cameraOrbit, resolveColor3D, sanitizeHeight, snapFlag, surfaceRevision, } from "./shared.js";
11
+ const DEFAULT_POSITION = { x: 15, y: 12, z: 18 };
12
+ /**
13
+ * A 3D function grapher — any number of `z = f(x, y)` surfaces over a helper
14
+ * grid, with axes.
15
+ *
16
+ * Extends {@link Canvas3D} rather than `Node3D`: a grapher is a *viewport* on a
17
+ * scene it owns entirely, not a thing placed inside somebody else's. `Canvas3D`
18
+ * owns the bridge from the 2D scene graph into the 3D renderer, so subclassing
19
+ * it means this class only has to describe *what* to draw. It overrides
20
+ * `buildScene3D`, the single seam both the real render and the asset
21
+ * declaration pass go through.
22
+ *
23
+ * <Graph3D
24
+ * width="fill" height="fill"
25
+ * equations={[{ id: 1, expression: "sin(sqrt(x^2 + y^2))", color: "#3B82F6" }]}
26
+ * />
27
+ *
28
+ * Every prop is a `@property`, so a constant, a `() => signal()` binding, and
29
+ * `set()`/`to()` on a ref all work and agree. The non-scalar props each declare
30
+ * a `mapper` (loose authored shape → one canonical internal shape) and a `tween`
31
+ * (how that shape interpolates) — without the tween, `to({ grid: … })` would
32
+ * hold the old value and snap at the end, which is the standard trap for an
33
+ * object-valued attribute.
34
+ *
35
+ * **There is no OrbitControls and no pointer.** The camera is driven by the
36
+ * node's own `orbit`/`elevation`/`zoom` props, which is what keeps every frame
37
+ * reproducible under scrubbing and export — see {@link Graph3DCamera}.
38
+ */
39
+ let Graph3D = class Graph3D extends Canvas3D {
40
+ constructor(props) {
41
+ super(props);
42
+ this.applyCameraDefaults(props);
43
+ // The backdrop is this node's own 2D fill: a `Canvas3D` composites its 3D
44
+ // pass over its fill layers, and there is no 3D background pass to reach
45
+ // for. Bound rather than copied so a tweened `background` carries, and so
46
+ // `transparentBackground` stays the *absence* of a fill — which is what
47
+ // lets the 2D scene behind actually show through.
48
+ if (props?.fill === undefined) {
49
+ this.applyProp("fill", () => this.transparentBackground ? [] : Fills.color(this.background));
50
+ }
51
+ }
52
+ /**
53
+ * Frame the scene at a spherical camera placement. Every axis is optional,
54
+ * so "pull back" and "spin round" stay separate intentions — see
55
+ * {@link OrbitTarget}.
56
+ */
57
+ orbitTo(args) {
58
+ return this.to({
59
+ data: orbitProps(args.data?.target ?? {}),
60
+ duration: args.duration,
61
+ easing: args.easing ?? easeInOut(),
62
+ });
63
+ }
64
+ /**
65
+ * Seeds `orbit`/`elevation`/`zoom` from `camera.initialPosition`, so a caller
66
+ * who gives a raw position gets that framing and one who gives none gets
67
+ * {@link DEFAULT_POSITION} — without having to restate the other two axes.
68
+ *
69
+ * Bound reactively rather than copied, so the default *tracks* the camera and
70
+ * a camera signal keeps working. Writing any of the three (a `set`, or the
71
+ * first frame of a `to`) replaces the binding with the explicit value, which
72
+ * is exactly the handover wanted.
73
+ */
74
+ applyCameraDefaults(props) {
75
+ const seed = () => orbitOf(this.camera.initialPosition);
76
+ if (props?.orbit === undefined) {
77
+ this.applyProp("orbit", () => seed().orbit);
78
+ }
79
+ if (props?.elevation === undefined) {
80
+ this.applyProp("elevation", () => seed().elevation);
81
+ }
82
+ if (props?.zoom === undefined) {
83
+ this.applyProp("zoom", () => seed().distance);
84
+ }
85
+ }
86
+ // ---- Drawing ------------------------------------------------------------
87
+ buildScene3D() {
88
+ const scene = new Scene3D();
89
+ const g3 = new Graphics3D();
90
+ const camera = this.camera;
91
+ const grid = this.grid;
92
+ const equations = this.equations;
93
+ // Polar placement is the camera's own vocabulary now, so these three pass
94
+ // straight through; `cameraOrbit` is the quarter turn between the stored
95
+ // zero and the camera's. `near`/`far` come from the scene's own bounds.
96
+ scene.perspective({
97
+ fov: 45,
98
+ orbit: cameraOrbit(this.orbit),
99
+ elevation: this.elevation,
100
+ // Clamped here rather than in the mapper: `zoom` and the limits are
101
+ // separate props, so either can be tweened and the clamp still holds.
102
+ distance: Math.min(Math.max(this.zoom, camera.minZoom), camera.maxZoom),
103
+ });
104
+ // The backdrop is the node's own 2D fill — a `Canvas3D` composites its 3D
105
+ // pass over its fill layers, and there is no 3D background pass to reach
106
+ // for. `transparentBackground` is therefore the *absence* of a fill rather
107
+ // than a clear colour, which is what the props mapper already emits.
108
+ // A soft ambient fill plus a key light and a weaker back light, so the
109
+ // underside of a surface stays readable when the camera orbits beneath it.
110
+ // A light's own parameters and its *placement* are separate arguments now —
111
+ // placement is a transform, and a transform is not a property of the lamp.
112
+ scene
113
+ .light({ type: "ambient", intensity: 0.4 })
114
+ .light({ type: "directional", intensity: 0.6 }, { position: [10, 20, 10] })
115
+ .light({ type: "directional", intensity: 0.4 }, { position: [-10, -20, -10] });
116
+ if (grid.showGrid)
117
+ this.addGrid(g3, grid);
118
+ if (grid.showAxes)
119
+ this.addAxes(g3, grid.axesSize);
120
+ const domain = this.domain;
121
+ const segments = Math.max(1, Math.round(this.segments));
122
+ const maxHeight = this.maxHeight;
123
+ equations.forEach((equation, index) => {
124
+ this.addSurface(g3, equation, index, domain, segments, maxHeight);
125
+ });
126
+ return scene.draw(g3);
127
+ }
128
+ /**
129
+ * One `z = f(x, y)` surface, keyed by equation id rather than by position in
130
+ * the op list — see {@link Graph3D.equations}.
131
+ */
132
+ addSurface(g3, equation, index, domain, segments, maxHeight) {
133
+ // Fully faded out (hidden, or the far end of a cross-fade): emit nothing.
134
+ // Safe to skip precisely because identity is the `key`, not the slot.
135
+ if (equation.opacity <= 0.001)
136
+ return;
137
+ const span = domain * 2;
138
+ const { sample, opacity } = equation;
139
+ const solid = opacity >= 1;
140
+ g3.mesh(Geo.parametric({
141
+ segments,
142
+ // The maths plane is (x, y) and the result is height, so maths y becomes
143
+ // the renderer's z and the result becomes its y.
144
+ vertex: (u, v) => {
145
+ const x = u * span - domain;
146
+ const z = v * span - domain;
147
+ return { x, y: sanitizeHeight(sample(x, z), maxHeight), z };
148
+ },
149
+ computeNormals: true, // without this the surface is flat-lit
150
+ // Exactly the three things the callback above closes over. Without it the
151
+ // renderer re-runs `vertex` over the whole grid every frame — ~6.6k calls
152
+ // per surface at the default `segments: 80`, plus normals and a buffer
153
+ // re-upload — and a frame is drawn for all sorts of reasons that leave the
154
+ // surface untouched: the camera orbiting, a gizmo drag on an unrelated
155
+ // node repainting at pointer rate, a tween elsewhere in the scene.
156
+ //
157
+ // A morph still re-evaluates as it must: `blendEquation` hands out a fresh
158
+ // closure per frame mid-tween, and tweening `domain` or `maxHeight` moves
159
+ // the number directly.
160
+ revision: surfaceRevision(sample, domain, maxHeight),
161
+ }), Mat.standard({
162
+ color: equation.color,
163
+ faces: "both", // a surface is infinitely thin; show both faces
164
+ roughness: 0.4,
165
+ metalness: 0.1,
166
+ // No `transparent`: the renderer derives blending from the opacity, so
167
+ // the flag and the value it was meant to accompany cannot disagree.
168
+ opacity,
169
+ // The fix for surfaces tearing holes in each other mid-fade. A
170
+ // translucent mesh that still writes depth occludes everything drawn
171
+ // after it, so a surface at 5% opacity renders as a near-invisible
172
+ // cut-out through the surfaces behind it — and it resolves only once the
173
+ // fade lands. Depth is written by fully opaque surfaces only; the rest
174
+ // blend, which is what asking for an opacity below 1 means.
175
+ depthWrite: solid,
176
+ }), {
177
+ key: `equation:${equation.id}`,
178
+ // The renderer sorts the transparent pass back-to-front by centroid
179
+ // distance — and every surface here is centred on the origin, so their
180
+ // sort keys are identical and the order is arbitrary, which flickers as
181
+ // the camera orbits. An explicit order makes it stable.
182
+ renderOrder: index,
183
+ });
184
+ }
185
+ /**
186
+ * The ground grid, as two line ops — the centre cross and everything else —
187
+ * because a line op carries a single colour and the centre lines are drawn
188
+ * differently from the rest.
189
+ */
190
+ addGrid(g3, grid) {
191
+ const divisions = Math.max(1, grid.divisions);
192
+ const half = grid.size / 2;
193
+ const step = grid.size / divisions;
194
+ const centre = [];
195
+ const lines = [];
196
+ for (let i = 0; i <= divisions; i++) {
197
+ const offset = -half + i * step;
198
+ // A line is a centre line when it passes through the origin. With an odd
199
+ // division count no line does, which is the conventional behaviour.
200
+ const isCentre = Math.abs(offset) < step / 1000;
201
+ const target = isCentre ? centre : lines;
202
+ target.push([-half, 0, offset], [half, 0, offset]); // along X
203
+ target.push([offset, 0, -half], [offset, 0, half]); // along Z
204
+ }
205
+ if (lines.length > 0) {
206
+ // No key: a conditionally-emitted op used to shift every later op's cache
207
+ // slot, and the reconciler keys a drawable by its content now.
208
+ g3.line({
209
+ points: lines,
210
+ segments: true,
211
+ stroke: { fill: grid.colorGrid },
212
+ });
213
+ }
214
+ if (centre.length > 0) {
215
+ g3.line({
216
+ points: centre,
217
+ segments: true,
218
+ stroke: { fill: grid.colorCenterLine },
219
+ });
220
+ }
221
+ }
222
+ /** X/Y/Z axes in red/green/blue, the conventional colouring. */
223
+ addAxes(g3, length) {
224
+ const axes = [
225
+ [[length, 0, 0], "#ff4d4d"],
226
+ [[0, length, 0], "#4ade80"],
227
+ [[0, 0, length], "#60a5fa"],
228
+ ];
229
+ for (const [end, color] of axes) {
230
+ g3.line({ points: [[0, 0, 0], end], stroke: { fill: color } });
231
+ }
232
+ }
233
+ };
234
+ __decorate([
235
+ property({ default: [], mapper: resolveEquations, tween: lerpEquations })
236
+ ], Graph3D.prototype, "equations", void 0);
237
+ __decorate([
238
+ property({ default: {}, mapper: resolveCamera, tween: lerpCamera })
239
+ ], Graph3D.prototype, "camera", void 0);
240
+ __decorate([
241
+ property({ default: {}, mapper: resolveGrid, tween: lerpGrid })
242
+ ], Graph3D.prototype, "grid", void 0);
243
+ __decorate([
244
+ property({ default: 10 })
245
+ ], Graph3D.prototype, "domain", void 0);
246
+ __decorate([
247
+ property({ default: 80 })
248
+ ], Graph3D.prototype, "segments", void 0);
249
+ __decorate([
250
+ property({ default: 20 })
251
+ ], Graph3D.prototype, "maxHeight", void 0);
252
+ __decorate([
253
+ property({
254
+ default: "#111827",
255
+ mapper: resolveColor3D,
256
+ tween: lerpColor3D,
257
+ })
258
+ ], Graph3D.prototype, "background", void 0);
259
+ __decorate([
260
+ property({ default: false, tween: snapFlag })
261
+ ], Graph3D.prototype, "transparentBackground", void 0);
262
+ __decorate([
263
+ property()
264
+ ], Graph3D.prototype, "orbit", void 0);
265
+ __decorate([
266
+ property()
267
+ ], Graph3D.prototype, "elevation", void 0);
268
+ __decorate([
269
+ property()
270
+ ], Graph3D.prototype, "zoom", void 0);
271
+ __decorate([
272
+ command({
273
+ args: [
274
+ {
275
+ key: "target", kind: "orbit", properties: {
276
+ orbit: { kind: "number", step: 1, unit: "°" },
277
+ elevation: { kind: "number", min: -89, max: 89, step: 1, unit: "°" },
278
+ zoom: { kind: "number", min: 0.2, max: 24, step: 0.1, unit: "×" },
279
+ },
280
+ },
281
+ ],
282
+ })
283
+ ], Graph3D.prototype, "orbitTo", null);
284
+ Graph3D = __decorate([
285
+ node({
286
+ key: "graph3d",
287
+ parentKey: "node",
288
+ forkable: true,
289
+ layout: {
290
+ children: "freeform",
291
+ defaultWidthMode: "fixed",
292
+ defaultHeightMode: "fixed",
293
+ acceptsChildren: true,
294
+ },
295
+ seed: {
296
+ width: 960,
297
+ height: 540,
298
+ },
299
+ })
300
+ ], Graph3D);
301
+ export { Graph3D };
302
+ // --- Mappers & tweens ------------------------------------------------------
303
+ /** Mapper for {@link Graph3D.equations}. */
304
+ function resolveEquations(input) {
305
+ if (!input)
306
+ return [];
307
+ const out = [];
308
+ for (const equation of input) {
309
+ const sample = compileExpressionCached(equation.expression);
310
+ if (!sample)
311
+ continue; // unparseable or empty — drop it, keep the rest
312
+ out.push({
313
+ id: String(equation.id),
314
+ sample,
315
+ color: resolveColor3D(equation.color),
316
+ opacity: equation.visible === false ? 0 : (equation.opacity ?? 1),
317
+ });
318
+ }
319
+ return out;
320
+ }
321
+ /**
322
+ * Mapper for {@link Graph3D.camera}.
323
+ *
324
+ * Merges onto `previous` rather than onto the bare defaults, which is what makes
325
+ * a partial write mean "change this much and leave the rest". It matters most on
326
+ * a `to` command, whose target states only the fields that command touches: a
327
+ * tween that raises `maxZoom` must not silently hand the framing back to
328
+ * {@link DEFAULT_POSITION} on its way.
329
+ */
330
+ function resolveCamera(input, previous) {
331
+ const base = previous ?? {
332
+ initialPosition: DEFAULT_POSITION,
333
+ minZoom: 0.1,
334
+ maxZoom: Number.POSITIVE_INFINITY,
335
+ };
336
+ return {
337
+ initialPosition: input?.initialPosition ?? base.initialPosition,
338
+ minZoom: input?.minZoom ?? base.minZoom,
339
+ maxZoom: input?.maxZoom ?? base.maxZoom,
340
+ };
341
+ }
342
+ /** Tween for {@link Graph3D.camera}. */
343
+ function lerpCamera(from, to, t) {
344
+ return {
345
+ initialPosition: {
346
+ x: lerpNumber(from.initialPosition.x, to.initialPosition.x, t),
347
+ y: lerpNumber(from.initialPosition.y, to.initialPosition.y, t),
348
+ z: lerpNumber(from.initialPosition.z, to.initialPosition.z, t),
349
+ },
350
+ // `maxZoom` defaults to Infinity, which a plain lerp turns into NaN.
351
+ minZoom: lerpFinite(from.minZoom, to.minZoom, t),
352
+ maxZoom: lerpFinite(from.maxZoom, to.maxZoom, t),
353
+ };
354
+ }
355
+ /** The grid a node with nothing said about it draws. */
356
+ const DEFAULT_GRID = {
357
+ showGrid: true,
358
+ size: 20,
359
+ divisions: 20,
360
+ colorCenterLine: resolveColor3D("#4b5563"),
361
+ colorGrid: resolveColor3D("#1f2937"),
362
+ showAxes: true,
363
+ axesSize: 10,
364
+ };
365
+ /** Mapper for {@link Graph3D.grid}. Merges onto `previous` — see {@link resolveCamera}. */
366
+ function resolveGrid(input, previous) {
367
+ const base = previous ?? DEFAULT_GRID;
368
+ return {
369
+ showGrid: input?.showGrid ?? base.showGrid,
370
+ size: input?.size ?? base.size,
371
+ divisions: input?.divisions ?? base.divisions,
372
+ colorCenterLine: input?.colorCenterLine === undefined
373
+ ? base.colorCenterLine
374
+ : resolveColor3D(input.colorCenterLine),
375
+ colorGrid: input?.colorGrid === undefined
376
+ ? base.colorGrid
377
+ : resolveColor3D(input.colorGrid),
378
+ showAxes: input?.showAxes ?? base.showAxes,
379
+ axesSize: input?.axesSize ?? base.axesSize,
380
+ };
381
+ }
382
+ /** Tween for {@link Graph3D.grid}. */
383
+ function lerpGrid(from, to, t) {
384
+ return {
385
+ showGrid: snapFlag(from.showGrid, to.showGrid, t),
386
+ size: lerpNumber(from.size, to.size, t),
387
+ divisions: lerpCount(from.divisions, to.divisions, t),
388
+ colorCenterLine: lerpColor3D(from.colorCenterLine, to.colorCenterLine, t),
389
+ colorGrid: lerpColor3D(from.colorGrid, to.colorGrid, t),
390
+ showAxes: snapFlag(from.showAxes, to.showAxes, t),
391
+ axesSize: lerpNumber(from.axesSize, to.axesSize, t),
392
+ };
393
+ }
394
+ /** Decomposes a raw camera position into spherical terms. */
395
+ function orbitOf(position) {
396
+ const distance = Math.hypot(position.x, position.y, position.z) || 1;
397
+ const sin = Math.min(1, Math.max(-1, position.y / distance));
398
+ return {
399
+ orbit: Math.atan2(position.z, position.x) * (180 / Math.PI),
400
+ elevation: Math.asin(sin) * (180 / Math.PI),
401
+ distance,
402
+ };
403
+ }
404
+ //# sourceMappingURL=graph3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph3d.js","sourceRoot":"","sources":["../../src/graph3d/graph3d.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n command,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Canvas3D,\n Fills,\n Geo,\n Graphics3D,\n Mat,\n Scene3D,\n lerpNumber,\n property,\n type Color,\n type NodeConfig,\n type NormalizedColor,\n type Canvas3DProps,\n type Vector3Input,\n} from \"@motionscript/core\"\nimport { orbitProps, type OrbitTarget } from \"@motionscript/core/component\"\n\nimport { compileExpressionCached } from \"./expression\"\nimport {\n lerpColor3D,\n lerpCount,\n lerpEquations,\n lerpFinite,\n cameraOrbit,\n resolveColor3D,\n sanitizeHeight,\n snapFlag,\n surfaceRevision,\n type EquationResolved,\n} from \"./shared\"\n\n// --- Authored props --------------------------------------------------------\n// The loose shapes a caller writes. Each has a matching `*Resolved` type below:\n// what the `@property` mapper turns it into and what the tween interpolates.\n\n/** One plotted surface, `z = f(x, y)`. */\nexport interface Graph3DEquation {\n /** Identity across list changes — see {@link Graph3D.equations}. */\n id: string | number\n /** The expression, evaluated as `z = f(x, y)` (e.g. `sin(x) + cos(y)`). */\n expression: string\n /** The mesh's colour: any {@link Color} — a CSS string or an RGBA tuple. */\n color: Color\n /** Whether the surface is drawn. Defaults to true. */\n visible?: boolean\n /**\n * How solid the mesh is, 0–1. Defaults to 1.\n *\n * Anything below 1 makes the surface blend rather than write depth, so\n * overlapping surfaces layer instead of occluding each other. Left at 1 the\n * surface is genuinely opaque and reads crisp — which is why that is the\n * default, and why fading one in or out still looks right on the way through.\n */\n opacity?: number\n}\n\n/**\n * Where the camera may go.\n *\n * Deliberately *not* OrbitControls. Those drive a camera from live mouse input,\n * and a rendered timeline has no pointer — worse, their damping is an\n * accumulator, so a scrubbed frame would differ from a played one and export\n * would stop being deterministic. What is honoured here is the framing\n * ({@link initialPosition}) and the limits; to *move* the camera, animate\n * {@link Graph3DProps.orbit} / {@link Graph3DProps.elevation} /\n * {@link Graph3DProps.zoom} from the timeline, which stays seekable.\n */\nexport interface Graph3DCamera {\n /** Initial camera position, which seeds the three spherical props. */\n initialPosition?: { x: number; y: number; z: number }\n /** Closest the camera may come to the origin. */\n minZoom?: number\n /** Furthest it may pull back. */\n maxZoom?: number\n}\n\n/** The helper grid and the axes drawn under the surfaces. */\nexport interface Graph3DGrid {\n /** Whether to draw the ground grid at all. Defaults to true. */\n showGrid?: boolean\n /** Total width and depth of the grid. Defaults to 20. */\n size?: number\n /** How many cells across. Defaults to 20. */\n divisions?: number\n /** Colour of the two lines through the origin. */\n colorCenterLine?: Color\n /** Colour of every other grid line. */\n colorGrid?: Color\n /** Whether to draw the X/Y/Z axes. Defaults to true. */\n showAxes?: boolean\n /** How far each axis reaches from the origin. Defaults to 10. */\n axesSize?: number\n}\n\nexport interface Graph3DProps extends Canvas3DProps {\n /** The surfaces to plot, each evaluated as `z = f(x, y)` over the domain. */\n equations: Graph3DEquation[]\n camera: Graph3DCamera\n grid: Graph3DGrid\n /** Half-width of the plotted domain: x and y run `-domain … +domain`. Default 10. */\n domain: number\n /**\n * Surface resolution per axis. Default 80, i.e. ~6.4k vertices per equation.\n * Lower it when plotting several surfaces at once.\n */\n segments: number\n /**\n * Clamp on `|z|`. Default 20. Without it a function with an asymptote (`1/x`,\n * `tan`) produces near-infinite vertices that stretch the mesh across the\n * whole scene and wreck the framing.\n */\n maxHeight: number\n /** Camera orbit about the vertical axis, in **degrees**. Animate this to spin. */\n orbit: number\n /** Camera elevation above the ground plane, in **degrees**. */\n elevation: number\n /** Camera distance from the origin, clamped by the camera's zoom limits. */\n zoom: number\n /** The 3D scene's background. */\n background: Color\n /** Skips the background entirely, letting the 2D scene behind show through. */\n transparentBackground: boolean\n}\n\n// --- Resolved props --------------------------------------------------------\n// Canonical, fully defaulted, interpolatable. Nothing here is optional, so the\n// builder never re-derives a default and the tweens never test for one.\n\n/** {@link Graph3DCamera} after {@link resolveCamera}. */\ninterface CameraResolved {\n initialPosition: { x: number; y: number; z: number }\n minZoom: number\n maxZoom: number\n}\n\n/** {@link Graph3DGrid} after {@link resolveGrid}. */\ninterface GridResolved {\n showGrid: boolean\n size: number\n divisions: number\n colorCenterLine: NormalizedColor\n colorGrid: NormalizedColor\n showAxes: boolean\n axesSize: number\n}\n\nconst DEFAULT_POSITION = { x: 15, y: 12, z: 18 }\n\n/**\n * A 3D function grapher — any number of `z = f(x, y)` surfaces over a helper\n * grid, with axes.\n *\n * Extends {@link Canvas3D} rather than `Node3D`: a grapher is a *viewport* on a\n * scene it owns entirely, not a thing placed inside somebody else's. `Canvas3D`\n * owns the bridge from the 2D scene graph into the 3D renderer, so subclassing\n * it means this class only has to describe *what* to draw. It overrides\n * `buildScene3D`, the single seam both the real render and the asset\n * declaration pass go through.\n *\n * <Graph3D\n * width=\"fill\" height=\"fill\"\n * equations={[{ id: 1, expression: \"sin(sqrt(x^2 + y^2))\", color: \"#3B82F6\" }]}\n * />\n *\n * Every prop is a `@property`, so a constant, a `() => signal()` binding, and\n * `set()`/`to()` on a ref all work and agree. The non-scalar props each declare\n * a `mapper` (loose authored shape → one canonical internal shape) and a `tween`\n * (how that shape interpolates) — without the tween, `to({ grid: … })` would\n * hold the old value and snap at the end, which is the standard trap for an\n * object-valued attribute.\n *\n * **There is no OrbitControls and no pointer.** The camera is driven by the\n * node's own `orbit`/`elevation`/`zoom` props, which is what keeps every frame\n * reproducible under scrubbing and export — see {@link Graph3DCamera}.\n */\n@node({\n key: \"graph3d\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: true,\n },\n seed: {\n width: 960,\n height: 540,\n },\n})\nexport class Graph3D extends Canvas3D<Graph3DProps> {\n /**\n * The plotted surfaces.\n *\n * The mapper compiles each expression **once per write** (memoised by source),\n * so the builder — which re-runs every frame — never re-parses; and it folds\n * `visible`/`opacity` into a single number, so hiding is expressible as a\n * fade. An expression that doesn't parse is dropped and the rest still draw,\n * which is what lets the inspector commit on every keystroke.\n *\n * The tween matches the two lists by `id` — see `lerpEquations`.\n */\n @property({ default: [], mapper: resolveEquations, tween: lerpEquations })\n declare equations: Graph3DEquation[]\n\n /** The camera limits, and the framing that seeds the three spherical props. */\n @property({ default: {}, mapper: resolveCamera, tween: lerpCamera })\n declare camera: Graph3DCamera\n\n /** The helper grid and axes. Flags snap at the end of a tween; the rest lerp. */\n @property({ default: {}, mapper: resolveGrid, tween: lerpGrid })\n declare grid: Graph3DGrid\n\n @property({ default: 10 }) declare domain: number\n /**\n * Surface resolution. Tweenable, but *structural*: a geometry is immutable, so\n * every intermediate value reallocates each surface's buffers. Set it once\n * unless the resolution change is itself the point.\n */\n @property({ default: 80 }) declare segments: number\n @property({ default: 20 }) declare maxHeight: number\n\n @property({\n default: \"#111827\",\n mapper: resolveColor3D,\n tween: lerpColor3D,\n })\n declare background: Color\n\n @property({ default: false, tween: snapFlag })\n declare transparentBackground: boolean\n\n // No `default:` — the baseline is derived from `camera.initialPosition` in\n // `applyCameraDefaults` below, which can't be expressed as a static default.\n @property() declare orbit: number\n @property() declare elevation: number\n @property() declare zoom: number\n\n constructor(props?: NodeConfig<Graph3D, Graph3DProps>) {\n super(props as NodeConfig<Canvas3D<Graph3DProps>, Graph3DProps>)\n this.applyCameraDefaults(props)\n // The backdrop is this node's own 2D fill: a `Canvas3D` composites its 3D\n // pass over its fill layers, and there is no 3D background pass to reach\n // for. Bound rather than copied so a tweened `background` carries, and so\n // `transparentBackground` stays the *absence* of a fill — which is what\n // lets the 2D scene behind actually show through.\n if (props?.fill === undefined) {\n this.applyProp(\"fill\", () =>\n this.transparentBackground ? [] : Fills.color(this.background)\n )\n }\n }\n\n /**\n * Frame the scene at a spherical camera placement. Every axis is optional,\n * so \"pull back\" and \"spin round\" stay separate intentions — see\n * {@link OrbitTarget}.\n */\n @command({\n args: [\n {\n key: \"target\", kind: \"orbit\", properties: {\n orbit: { kind: \"number\", step: 1, unit: \"°\" },\n elevation: { kind: \"number\", min: -89, max: 89, step: 1, unit: \"°\" },\n zoom: { kind: \"number\", min: 0.2, max: 24, step: 0.1, unit: \"×\" },\n },\n },\n ],\n })\n orbitTo(args: CommandArgs<{ target: OrbitTarget }> & { duration: number }): Command<Graph3DProps> {\n return this.to({\n data: orbitProps(args.data?.target ?? {}) as Partial<Graph3DProps>,\n duration: args.duration,\n easing: args.easing ?? easeInOut(),\n })\n }\n\n /**\n * Seeds `orbit`/`elevation`/`zoom` from `camera.initialPosition`, so a caller\n * who gives a raw position gets that framing and one who gives none gets\n * {@link DEFAULT_POSITION} — without having to restate the other two axes.\n *\n * Bound reactively rather than copied, so the default *tracks* the camera and\n * a camera signal keeps working. Writing any of the three (a `set`, or the\n * first frame of a `to`) replaces the binding with the explicit value, which\n * is exactly the handover wanted.\n */\n private applyCameraDefaults(\n props?: NodeConfig<Graph3D, Graph3DProps>\n ): void {\n const seed = (): Orbit3D =>\n orbitOf((this.camera as CameraResolved).initialPosition)\n\n if (props?.orbit === undefined) {\n this.applyProp(\"orbit\", () => seed().orbit)\n }\n if (props?.elevation === undefined) {\n this.applyProp(\"elevation\", () => seed().elevation)\n }\n if (props?.zoom === undefined) {\n this.applyProp(\"zoom\", () => seed().distance)\n }\n }\n\n\n // ---- Drawing ------------------------------------------------------------\n\n protected override buildScene3D(): Scene3D {\n const scene = new Scene3D()\n const g3 = new Graphics3D()\n const camera = this.camera as unknown as CameraResolved\n const grid = this.grid as unknown as GridResolved\n const equations = this.equations as unknown as EquationResolved[]\n\n // Polar placement is the camera's own vocabulary now, so these three pass\n // straight through; `cameraOrbit` is the quarter turn between the stored\n // zero and the camera's. `near`/`far` come from the scene's own bounds.\n scene.perspective({\n fov: 45,\n orbit: cameraOrbit(this.orbit),\n elevation: this.elevation,\n // Clamped here rather than in the mapper: `zoom` and the limits are\n // separate props, so either can be tweened and the clamp still holds.\n distance: Math.min(Math.max(this.zoom, camera.minZoom), camera.maxZoom),\n })\n\n // The backdrop is the node's own 2D fill — a `Canvas3D` composites its 3D\n // pass over its fill layers, and there is no 3D background pass to reach\n // for. `transparentBackground` is therefore the *absence* of a fill rather\n // than a clear colour, which is what the props mapper already emits.\n\n // A soft ambient fill plus a key light and a weaker back light, so the\n // underside of a surface stays readable when the camera orbits beneath it.\n // A light's own parameters and its *placement* are separate arguments now —\n // placement is a transform, and a transform is not a property of the lamp.\n scene\n .light({ type: \"ambient\", intensity: 0.4 })\n .light({ type: \"directional\", intensity: 0.6 }, { position: [10, 20, 10] })\n .light(\n { type: \"directional\", intensity: 0.4 },\n { position: [-10, -20, -10] }\n )\n\n if (grid.showGrid) this.addGrid(g3, grid)\n if (grid.showAxes) this.addAxes(g3, grid.axesSize)\n\n const domain = this.domain\n const segments = Math.max(1, Math.round(this.segments))\n const maxHeight = this.maxHeight\n equations.forEach((equation, index) => {\n this.addSurface(g3, equation, index, domain, segments, maxHeight)\n })\n\n return scene.draw(g3)\n }\n\n /**\n * One `z = f(x, y)` surface, keyed by equation id rather than by position in\n * the op list — see {@link Graph3D.equations}.\n */\n private addSurface(\n g3: Graphics3D,\n equation: EquationResolved,\n index: number,\n domain: number,\n segments: number,\n maxHeight: number\n ): void {\n // Fully faded out (hidden, or the far end of a cross-fade): emit nothing.\n // Safe to skip precisely because identity is the `key`, not the slot.\n if (equation.opacity <= 0.001) return\n\n const span = domain * 2\n const { sample, opacity } = equation\n const solid = opacity >= 1\n\n g3.mesh(\n Geo.parametric({\n segments,\n // The maths plane is (x, y) and the result is height, so maths y becomes\n // the renderer's z and the result becomes its y.\n vertex: (u, v) => {\n const x = u * span - domain\n const z = v * span - domain\n return { x, y: sanitizeHeight(sample(x, z), maxHeight), z }\n },\n computeNormals: true, // without this the surface is flat-lit\n // Exactly the three things the callback above closes over. Without it the\n // renderer re-runs `vertex` over the whole grid every frame — ~6.6k calls\n // per surface at the default `segments: 80`, plus normals and a buffer\n // re-upload — and a frame is drawn for all sorts of reasons that leave the\n // surface untouched: the camera orbiting, a gizmo drag on an unrelated\n // node repainting at pointer rate, a tween elsewhere in the scene.\n //\n // A morph still re-evaluates as it must: `blendEquation` hands out a fresh\n // closure per frame mid-tween, and tweening `domain` or `maxHeight` moves\n // the number directly.\n revision: surfaceRevision(sample, domain, maxHeight),\n }),\n Mat.standard({\n color: equation.color,\n faces: \"both\", // a surface is infinitely thin; show both faces\n roughness: 0.4,\n metalness: 0.1,\n // No `transparent`: the renderer derives blending from the opacity, so\n // the flag and the value it was meant to accompany cannot disagree.\n opacity,\n // The fix for surfaces tearing holes in each other mid-fade. A\n // translucent mesh that still writes depth occludes everything drawn\n // after it, so a surface at 5% opacity renders as a near-invisible\n // cut-out through the surfaces behind it — and it resolves only once the\n // fade lands. Depth is written by fully opaque surfaces only; the rest\n // blend, which is what asking for an opacity below 1 means.\n depthWrite: solid,\n }),\n {\n key: `equation:${equation.id}`,\n // The renderer sorts the transparent pass back-to-front by centroid\n // distance — and every surface here is centred on the origin, so their\n // sort keys are identical and the order is arbitrary, which flickers as\n // the camera orbits. An explicit order makes it stable.\n renderOrder: index,\n }\n )\n }\n\n /**\n * The ground grid, as two line ops — the centre cross and everything else —\n * because a line op carries a single colour and the centre lines are drawn\n * differently from the rest.\n */\n private addGrid(g3: Graphics3D, grid: GridResolved): void {\n const divisions = Math.max(1, grid.divisions)\n const half = grid.size / 2\n const step = grid.size / divisions\n\n const centre: Vector3Input[] = []\n const lines: Vector3Input[] = []\n\n for (let i = 0; i <= divisions; i++) {\n const offset = -half + i * step\n // A line is a centre line when it passes through the origin. With an odd\n // division count no line does, which is the conventional behaviour.\n const isCentre = Math.abs(offset) < step / 1000\n const target = isCentre ? centre : lines\n target.push([-half, 0, offset], [half, 0, offset]) // along X\n target.push([offset, 0, -half], [offset, 0, half]) // along Z\n }\n\n if (lines.length > 0) {\n // No key: a conditionally-emitted op used to shift every later op's cache\n // slot, and the reconciler keys a drawable by its content now.\n g3.line({\n points: lines,\n segments: true,\n stroke: { fill: grid.colorGrid },\n })\n }\n if (centre.length > 0) {\n g3.line({\n points: centre,\n segments: true,\n stroke: { fill: grid.colorCenterLine },\n })\n }\n }\n\n /** X/Y/Z axes in red/green/blue, the conventional colouring. */\n private addAxes(g3: Graphics3D, length: number): void {\n const axes: Array<[Vector3Input, string]> = [\n [[length, 0, 0], \"#ff4d4d\"],\n [[0, length, 0], \"#4ade80\"],\n [[0, 0, length], \"#60a5fa\"],\n ]\n\n for (const [end, color] of axes) {\n g3.line({ points: [[0, 0, 0], end], stroke: { fill: color } })\n }\n }\n}\n\n// --- Mappers & tweens ------------------------------------------------------\n\n/** Mapper for {@link Graph3D.equations}. */\nfunction resolveEquations(\n input: Graph3DEquation[] | undefined\n): EquationResolved[] {\n if (!input) return []\n const out: EquationResolved[] = []\n for (const equation of input) {\n const sample = compileExpressionCached(equation.expression)\n if (!sample) continue // unparseable or empty — drop it, keep the rest\n out.push({\n id: String(equation.id),\n sample,\n color: resolveColor3D(equation.color),\n opacity: equation.visible === false ? 0 : (equation.opacity ?? 1),\n })\n }\n return out\n}\n\n/**\n * Mapper for {@link Graph3D.camera}.\n *\n * Merges onto `previous` rather than onto the bare defaults, which is what makes\n * a partial write mean \"change this much and leave the rest\". It matters most on\n * a `to` command, whose target states only the fields that command touches: a\n * tween that raises `maxZoom` must not silently hand the framing back to\n * {@link DEFAULT_POSITION} on its way.\n */\nfunction resolveCamera(\n input: Graph3DCamera | undefined,\n previous?: CameraResolved\n): CameraResolved {\n const base = previous ?? {\n initialPosition: DEFAULT_POSITION,\n minZoom: 0.1,\n maxZoom: Number.POSITIVE_INFINITY,\n }\n return {\n initialPosition: input?.initialPosition ?? base.initialPosition,\n minZoom: input?.minZoom ?? base.minZoom,\n maxZoom: input?.maxZoom ?? base.maxZoom,\n }\n}\n\n/** Tween for {@link Graph3D.camera}. */\nfunction lerpCamera(\n from: CameraResolved,\n to: CameraResolved,\n t: number\n): CameraResolved {\n return {\n initialPosition: {\n x: lerpNumber(from.initialPosition.x, to.initialPosition.x, t),\n y: lerpNumber(from.initialPosition.y, to.initialPosition.y, t),\n z: lerpNumber(from.initialPosition.z, to.initialPosition.z, t),\n },\n // `maxZoom` defaults to Infinity, which a plain lerp turns into NaN.\n minZoom: lerpFinite(from.minZoom, to.minZoom, t),\n maxZoom: lerpFinite(from.maxZoom, to.maxZoom, t),\n }\n}\n\n/** The grid a node with nothing said about it draws. */\nconst DEFAULT_GRID: GridResolved = {\n showGrid: true,\n size: 20,\n divisions: 20,\n colorCenterLine: resolveColor3D(\"#4b5563\"),\n colorGrid: resolveColor3D(\"#1f2937\"),\n showAxes: true,\n axesSize: 10,\n}\n\n/** Mapper for {@link Graph3D.grid}. Merges onto `previous` — see {@link resolveCamera}. */\nfunction resolveGrid(\n input: Graph3DGrid | undefined,\n previous?: GridResolved\n): GridResolved {\n const base = previous ?? DEFAULT_GRID\n return {\n showGrid: input?.showGrid ?? base.showGrid,\n size: input?.size ?? base.size,\n divisions: input?.divisions ?? base.divisions,\n colorCenterLine:\n input?.colorCenterLine === undefined\n ? base.colorCenterLine\n : resolveColor3D(input.colorCenterLine),\n colorGrid:\n input?.colorGrid === undefined\n ? base.colorGrid\n : resolveColor3D(input.colorGrid),\n showAxes: input?.showAxes ?? base.showAxes,\n axesSize: input?.axesSize ?? base.axesSize,\n }\n}\n\n/** Tween for {@link Graph3D.grid}. */\nfunction lerpGrid(from: GridResolved, to: GridResolved, t: number): GridResolved {\n return {\n showGrid: snapFlag(from.showGrid, to.showGrid, t),\n size: lerpNumber(from.size, to.size, t),\n divisions: lerpCount(from.divisions, to.divisions, t),\n colorCenterLine: lerpColor3D(from.colorCenterLine, to.colorCenterLine, t),\n colorGrid: lerpColor3D(from.colorGrid, to.colorGrid, t),\n showAxes: snapFlag(from.showAxes, to.showAxes, t),\n axesSize: lerpNumber(from.axesSize, to.axesSize, t),\n }\n}\n\n/** A camera placement in spherical terms. */\ninterface Orbit3D {\n orbit: number\n elevation: number\n distance: number\n}\n\n/** Decomposes a raw camera position into spherical terms. */\nfunction orbitOf(position: { x: number; y: number; z: number }): Orbit3D {\n const distance = Math.hypot(position.x, position.y, position.z) || 1\n const sin = Math.min(1, Math.max(-1, position.y / distance))\n return {\n orbit: Math.atan2(position.z, position.x) * (180 / Math.PI),\n elevation: Math.asin(sin) * (180 / Math.PI),\n distance,\n }\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The Graph 3D node — a **native** node type (see `node-types.ts` /
3
+ * `NODE_CLASS_BY_KEY`) like the chart family beside it: built, themed and
4
+ * animated from the inspector rather than loaded as a code package.
5
+ *
6
+ * The one node in the app that draws in three dimensions. It plots any number of
7
+ * `z = f(x, y)` surfaces over a helper grid, sampling each from an expression
8
+ * the author types — which is why its own little language lives here
9
+ * (`expression.ts`) rather than reaching for a maths library: an expression is a
10
+ * value stored in a scene document, so compiling it as JavaScript would make
11
+ * every scene file an execution vector, and a 100×100 surface is ten thousand
12
+ * evaluations per equation per frame, which a general evaluator would not
13
+ * survive.
14
+ *
15
+ * There is no OrbitControls here and no pointer: the camera is three tweenable
16
+ * numbers, so every frame is reproducible under scrubbing and export. See
17
+ * {@link Graph3DCamera}.
18
+ */
19
+ export * from "./expression.js";
20
+ export * from "./graph3d.js";
21
+ export * from "./shared.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graph3d/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The Graph 3D node — a **native** node type (see `node-types.ts` /
3
+ * `NODE_CLASS_BY_KEY`) like the chart family beside it: built, themed and
4
+ * animated from the inspector rather than loaded as a code package.
5
+ *
6
+ * The one node in the app that draws in three dimensions. It plots any number of
7
+ * `z = f(x, y)` surfaces over a helper grid, sampling each from an expression
8
+ * the author types — which is why its own little language lives here
9
+ * (`expression.ts`) rather than reaching for a maths library: an expression is a
10
+ * value stored in a scene document, so compiling it as JavaScript would make
11
+ * every scene file an execution vector, and a 100×100 surface is ten thousand
12
+ * evaluations per equation per frame, which a general evaluator would not
13
+ * survive.
14
+ *
15
+ * There is no OrbitControls here and no pointer: the camera is three tweenable
16
+ * numbers, so every frame is reproducible under scrubbing and export. See
17
+ * {@link Graph3DCamera}.
18
+ */
19
+ export * from "./expression.js";
20
+ export * from "./graph3d.js";
21
+ export * from "./shared.js";
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graph3d/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC","sourcesContent":["/**\n * The Graph 3D node — a **native** node type (see `node-types.ts` /\n * `NODE_CLASS_BY_KEY`) like the chart family beside it: built, themed and\n * animated from the inspector rather than loaded as a code package.\n *\n * The one node in the app that draws in three dimensions. It plots any number of\n * `z = f(x, y)` surfaces over a helper grid, sampling each from an expression\n * the author types — which is why its own little language lives here\n * (`expression.ts`) rather than reaching for a maths library: an expression is a\n * value stored in a scene document, so compiling it as JavaScript would make\n * every scene file an execution vector, and a 100×100 surface is ten thousand\n * evaluations per equation per frame, which a general evaluator would not\n * survive.\n *\n * There is no OrbitControls here and no pointer: the camera is three tweenable\n * numbers, so every frame is reproducible under scrubbing and export. See\n * {@link Graph3DCamera}.\n */\nexport * from \"./expression\";\nexport * from \"./graph3d\";\nexport * from \"./shared\";\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The mappers, tweens and small maths the {@link Graph3D} node is built from.
3
+ *
4
+ * These exist because a `@property` is only as good as the `mapper`/`tween` it
5
+ * is declared with: the mapper turns a loose authored value (a CSS colour, a
6
+ * partly-filled options bag) into one canonical internal shape, and the tween is
7
+ * what makes `node.to({ … })` *interpolate* that shape instead of holding the
8
+ * old value and snapping at the end. Core's own attributes are built the same
9
+ * way — see `cornerRadiusOps` behind `Rect.cornerRadius`.
10
+ *
11
+ * The colour, number and camera helpers this used to define now live in
12
+ * `nodes/view3d-kit`, because the Protein node needs the same six and none of
13
+ * them were ever about graphs. The equation list's shape and its tween moved the
14
+ * same way, into `nodes/graph-kit`, once the 2D graph needed both. They are
15
+ * re-exported here so this stays the one import a graph3d module reaches for.
16
+ */
17
+ import { type GraphEquationResolved } from "../kit/equations.js";
18
+ export { lerpColor3D, lerpCount, lerpFinite, cameraOrbit, resolveColor3D, snapFlag, type Orbit, } from "@motionscript/core/component";
19
+ import type { CompiledExpression } from "./expression.js";
20
+ /**
21
+ * Makes a sampled height safe to put in a vertex buffer.
22
+ *
23
+ * `NaN` (from `sqrt` of a negative, `log` of zero) poisons a whole triangle and
24
+ * leaves a hole; an asymptote (`1/x`, `tan`) produces values large enough to
25
+ * stretch the mesh off screen and wreck the camera framing. Both are flattened
26
+ * rather than dropped, so the surface stays continuous.
27
+ */
28
+ export declare function sanitizeHeight(value: number, maxHeight: number): number;
29
+ /**
30
+ * One surface after {@link Graph3D}'s mapper has run: compiled, resolved and
31
+ * fully defaulted, so the per-frame builder never re-derives anything and the
32
+ * tween never tests for an absent field.
33
+ */
34
+ export type EquationResolved = GraphEquationResolved<CompiledExpression>;
35
+ /**
36
+ * Tween for the equation list — the shared match-by-id walk, with the one thing
37
+ * a surface does differently: mid-morph its *height* is sampled from both
38
+ * functions and blended, so it deforms into the new one rather than popping.
39
+ *
40
+ * The blend deliberately returns a **fresh closure** per frame, which is what
41
+ * {@link surfaceRevision} reads as "re-evaluate this mesh".
42
+ */
43
+ export declare function lerpEquations(from: EquationResolved[], to: EquationResolved[], t: number): EquationResolved[];
44
+ /**
45
+ * The `revision` for one surface: a value that changes exactly when its vertex
46
+ * callback would return something different.
47
+ *
48
+ * The callback in `Graph3D.addSurface` closes over three things — the compiled
49
+ * expression, the domain (as `span`/`domain`) and `maxHeight` — so those three
50
+ * are what this covers, and nothing else. `segments` is deliberately absent: it
51
+ * is plain data on the descriptor that the renderer already compares itself.
52
+ *
53
+ * Mixed by FNV-1a over the three values' text rather than by arithmetic, because
54
+ * `domain` and `maxHeight` are tweenable floats and there is no cheap numeric
55
+ * combination of two floats and an int that doesn't collide somewhere. A
56
+ * collision here would show as a surface that refuses to update, which is a
57
+ * miserable bug to find; the string is a few dozen characters per surface per
58
+ * frame, against the ~6.6k vertex evaluations it exists to avoid.
59
+ */
60
+ export declare function surfaceRevision(sample: CompiledExpression, domain: number, maxHeight: number): number;
61
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/graph3d/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,WAAW,EACX,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EACd,QAAQ,EACR,KAAK,KAAK,GACX,MAAM,8BAA8B,CAAA;AAErC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAItD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGvE;AAID;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;AAExE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,gBAAgB,EAAE,EACxB,EAAE,EAAE,gBAAgB,EAAE,EACtB,CAAC,EAAE,MAAM,GACR,gBAAgB,EAAE,CAEpB;AAwCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR"}