@fnndsc/orrery 0.1.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 (116) hide show
  1. package/README.md +12 -0
  2. package/dist/controls/cameraRig.d.ts +223 -0
  3. package/dist/controls/cameraRig.js +353 -0
  4. package/dist/controls/cameraRig.js.map +1 -0
  5. package/dist/controls/gestures.d.ts +133 -0
  6. package/dist/controls/gestures.js +282 -0
  7. package/dist/controls/gestures.js.map +1 -0
  8. package/dist/controls/index.d.ts +9 -0
  9. package/dist/controls/index.js +10 -0
  10. package/dist/controls/index.js.map +1 -0
  11. package/dist/controls/picker.d.ts +117 -0
  12. package/dist/controls/picker.js +167 -0
  13. package/dist/controls/picker.js.map +1 -0
  14. package/dist/controls/replay.d.ts +94 -0
  15. package/dist/controls/replay.js +147 -0
  16. package/dist/controls/replay.js.map +1 -0
  17. package/dist/draw/censusField.d.ts +113 -0
  18. package/dist/draw/censusField.js +241 -0
  19. package/dist/draw/censusField.js.map +1 -0
  20. package/dist/draw/constants.d.ts +13 -0
  21. package/dist/draw/constants.js +14 -0
  22. package/dist/draw/constants.js.map +1 -0
  23. package/dist/draw/geometry.d.ts +33 -0
  24. package/dist/draw/geometry.js +55 -0
  25. package/dist/draw/geometry.js.map +1 -0
  26. package/dist/draw/handoff.d.ts +11 -0
  27. package/dist/draw/handoff.js +12 -0
  28. package/dist/draw/handoff.js.map +1 -0
  29. package/dist/draw/handoffField.d.ts +74 -0
  30. package/dist/draw/handoffField.js +151 -0
  31. package/dist/draw/handoffField.js.map +1 -0
  32. package/dist/draw/index.d.ts +20 -0
  33. package/dist/draw/index.js +21 -0
  34. package/dist/draw/index.js.map +1 -0
  35. package/dist/draw/labelField.d.ts +63 -0
  36. package/dist/draw/labelField.js +149 -0
  37. package/dist/draw/labelField.js.map +1 -0
  38. package/dist/draw/lamps.d.ts +16 -0
  39. package/dist/draw/lamps.js +17 -0
  40. package/dist/draw/lamps.js.map +1 -0
  41. package/dist/draw/nebula.d.ts +8 -0
  42. package/dist/draw/nebula.js +28 -0
  43. package/dist/draw/nebula.js.map +1 -0
  44. package/dist/draw/palette.d.ts +41 -0
  45. package/dist/draw/palette.js +32 -0
  46. package/dist/draw/palette.js.map +1 -0
  47. package/dist/draw/sphereField.d.ts +98 -0
  48. package/dist/draw/sphereField.js +168 -0
  49. package/dist/draw/sphereField.js.map +1 -0
  50. package/dist/draw/starField.d.ts +127 -0
  51. package/dist/draw/starField.js +267 -0
  52. package/dist/draw/starField.js.map +1 -0
  53. package/dist/draw/stars.d.ts +65 -0
  54. package/dist/draw/stars.js +132 -0
  55. package/dist/draw/stars.js.map +1 -0
  56. package/dist/draw/timing.d.ts +11 -0
  57. package/dist/draw/timing.js +12 -0
  58. package/dist/draw/timing.js.map +1 -0
  59. package/dist/draw/tubeField.d.ts +131 -0
  60. package/dist/draw/tubeField.js +352 -0
  61. package/dist/draw/tubeField.js.map +1 -0
  62. package/dist/draw/tubes.d.ts +41 -0
  63. package/dist/draw/tubes.js +116 -0
  64. package/dist/draw/tubes.js.map +1 -0
  65. package/dist/index.d.ts +15 -0
  66. package/dist/index.js +15 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/layout/accretion.d.ts +31 -0
  69. package/dist/layout/accretion.js +384 -0
  70. package/dist/layout/accretion.js.map +1 -0
  71. package/dist/layout/constellations.d.ts +25 -0
  72. package/dist/layout/constellations.js +197 -0
  73. package/dist/layout/constellations.js.map +1 -0
  74. package/dist/layout/galaxy.d.ts +12 -0
  75. package/dist/layout/galaxy.js +99 -0
  76. package/dist/layout/galaxy.js.map +1 -0
  77. package/dist/layout/hierarchy.d.ts +45 -0
  78. package/dist/layout/hierarchy.js +464 -0
  79. package/dist/layout/hierarchy.js.map +1 -0
  80. package/dist/layout/index.d.ts +20 -0
  81. package/dist/layout/index.js +21 -0
  82. package/dist/layout/index.js.map +1 -0
  83. package/dist/layout/layoutInput.d.ts +29 -0
  84. package/dist/layout/layoutInput.js +45 -0
  85. package/dist/layout/layoutInput.js.map +1 -0
  86. package/dist/layout/molecule.d.ts +64 -0
  87. package/dist/layout/molecule.js +155 -0
  88. package/dist/layout/molecule.js.map +1 -0
  89. package/dist/layout/pull.d.ts +94 -0
  90. package/dist/layout/pull.js +137 -0
  91. package/dist/layout/pull.js.map +1 -0
  92. package/dist/layout/ranked.d.ts +75 -0
  93. package/dist/layout/ranked.js +148 -0
  94. package/dist/layout/ranked.js.map +1 -0
  95. package/dist/layout/registry.d.ts +23 -0
  96. package/dist/layout/registry.js +165 -0
  97. package/dist/layout/registry.js.map +1 -0
  98. package/dist/layout/seeded.d.ts +32 -0
  99. package/dist/layout/seeded.js +48 -0
  100. package/dist/layout/seeded.js.map +1 -0
  101. package/dist/layout/types.d.ts +81 -0
  102. package/dist/layout/types.js +14 -0
  103. package/dist/layout/types.js.map +1 -0
  104. package/dist/scene/index.d.ts +6 -0
  105. package/dist/scene/index.js +7 -0
  106. package/dist/scene/index.js.map +1 -0
  107. package/dist/scene/orrery.d.ts +592 -0
  108. package/dist/scene/orrery.js +1555 -0
  109. package/dist/scene/orrery.js.map +1 -0
  110. package/dist/types/encoding.d.ts +43 -0
  111. package/dist/types/encoding.js +7 -0
  112. package/dist/types/encoding.js.map +1 -0
  113. package/dist/types/space.d.ts +12 -0
  114. package/dist/types/space.js +10 -0
  115. package/dist/types/space.js.map +1 -0
  116. package/package.json +57 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @file Stars: every node a point of light, sized true to the sphere it stands for. Error-free stars glow additively; a star with any errored share is drawn solid over the glow, so failure always shows through.
3
+ *
4
+ * @module
5
+ */
6
+ import * as THREE from 'three';
7
+ import { DIM_OPACITY } from './constants.js';
8
+ /** A star's smallest size on screen, in CSS pixels: no node ever vanishes. */
9
+ export const STAR_FLOOR_PX = 2;
10
+ /** A star never swells past this many CSS pixels: near the camera a feed is spheres, not a glow. */
11
+ export const STAR_CAP_PX = 28;
12
+ /** A star's sprite spans this many times its sphere's diameter: core plus glow. */
13
+ export const STAR_GLOW = 1.8;
14
+ export const STAR_VERTEX = `
15
+ attribute float radius;
16
+ attribute float alpha;
17
+ attribute vec3 tint;
18
+ attribute float flash;
19
+ uniform float scale;
20
+ uniform float floorPx;
21
+ uniform float capPx;
22
+ varying vec3 vTint;
23
+ varying float vAlpha;
24
+ varying float vFlash;
25
+ void main() {
26
+ vec4 mv = modelViewMatrix * vec4(position, 1.0);
27
+ gl_Position = projectionMatrix * mv;
28
+ float px = ${STAR_GLOW.toFixed(1)} * 2.0 * radius * scale / max(0.0001, -mv.z);
29
+ // An arrival flashes: the star swells past its cap for a moment.
30
+ gl_PointSize = min(max(px, floorPx), capPx) * (1.0 + 1.6 * flash);
31
+ vTint = tint;
32
+ vAlpha = alpha;
33
+ vFlash = flash;
34
+ }`;
35
+ /** The glow: a bright core falling off to nothing at the sprite's edge. */
36
+ export const STAR_FRAGMENT_GLOW = `
37
+ varying vec3 vTint;
38
+ varying float vAlpha;
39
+ varying float vFlash;
40
+ void main() {
41
+ float d = length(gl_PointCoord - vec2(0.5)) * 2.0;
42
+ if (d > 1.0) discard;
43
+ float core = smoothstep(0.6, 0.0, d);
44
+ float halo = pow(1.0 - d, 2.2) * 0.55;
45
+ float a = clamp(core + halo, 0.0, 1.0) * vAlpha;
46
+ vec3 c = mix(vTint, vec3(1.0), vFlash * 0.6);
47
+ gl_FragColor = vec4(c * a, a);
48
+ }`;
49
+ /**
50
+ * The ring: a star of another kind — a thin bright circle round a small
51
+ * core, so a plugin reads apart from the feeds around it.
52
+ */
53
+ export const STAR_FRAGMENT_RING = `
54
+ varying vec3 vTint;
55
+ varying float vAlpha;
56
+ varying float vFlash;
57
+ void main() {
58
+ float d = length(gl_PointCoord - vec2(0.5)) * 2.0;
59
+ if (d > 1.0) discard;
60
+ float ring = smoothstep(0.62, 0.72, d) * smoothstep(0.92, 0.82, d);
61
+ float core = smoothstep(0.28, 0.0, d);
62
+ float a = clamp(ring + core, 0.0, 1.0) * vAlpha;
63
+ gl_FragColor = vec4(mix(vTint, vec3(1.0), 0.35 + vFlash * 0.4), a);
64
+ }`;
65
+ /** The ember: an errored star drawn solid over the glow, so red stays red. */
66
+ export const STAR_FRAGMENT_EMBER = `
67
+ varying vec3 vTint;
68
+ varying float vAlpha;
69
+ varying float vFlash;
70
+ void main() {
71
+ float d = length(gl_PointCoord - vec2(0.5)) * 2.0;
72
+ if (d > 1.0) discard;
73
+ float a = smoothstep(1.0, 0.35, d) * vAlpha;
74
+ gl_FragColor = vec4(mix(vTint, vec3(1.0), vFlash * 0.6), a);
75
+ }`;
76
+ /**
77
+ * Builds one layer of stars — the glow or the embers — as one draw call.
78
+ * Each entry learns its slot in the layer.
79
+ *
80
+ * @param entries - The layer's stars.
81
+ * @param ember - Whether this is the ember layer (drawn solid, over the glow).
82
+ * @param layerIndex - The layer's index, recorded on each entry.
83
+ * @param pixelRatio - The display's pixel ratio, for the size floor and cap.
84
+ * @returns The layer, not yet added to anything.
85
+ */
86
+ export function starLayer_make(entries, ember, layerIndex, pixelRatio, ring = false) {
87
+ const positions = new Float32Array(entries.length * 3);
88
+ const tints = new Float32Array(entries.length * 3);
89
+ const radii = new Float32Array(entries.length);
90
+ const alphas = new Float32Array(entries.length);
91
+ entries.forEach((entry, i) => {
92
+ entry.layer = layerIndex;
93
+ entry.slot = i;
94
+ positions.set([entry.position.x, entry.position.y, entry.position.z], i * 3);
95
+ tints.set([entry.color.r, entry.color.g, entry.color.b], i * 3);
96
+ radii[i] = entry.radius;
97
+ alphas[i] = entry.dim ? DIM_OPACITY * 1.5 : 1;
98
+ });
99
+ const geometry = new THREE.BufferGeometry();
100
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
101
+ geometry.setAttribute('tint', new THREE.BufferAttribute(tints, 3));
102
+ geometry.setAttribute('radius', new THREE.BufferAttribute(radii, 1));
103
+ const alpha = new THREE.BufferAttribute(alphas, 1);
104
+ geometry.setAttribute('alpha', alpha);
105
+ const flash = new THREE.BufferAttribute(new Float32Array(entries.length), 1);
106
+ geometry.setAttribute('flash', flash);
107
+ const material = new THREE.ShaderMaterial({
108
+ uniforms: { scale: { value: 1 }, floorPx: { value: STAR_FLOOR_PX * pixelRatio }, capPx: { value: STAR_CAP_PX * pixelRatio } },
109
+ vertexShader: STAR_VERTEX,
110
+ fragmentShader: ring ? STAR_FRAGMENT_RING : ember ? STAR_FRAGMENT_EMBER : STAR_FRAGMENT_GLOW,
111
+ transparent: true,
112
+ depthWrite: false,
113
+ blending: ember || ring ? THREE.NormalBlending : THREE.AdditiveBlending,
114
+ });
115
+ const points = new THREE.Points(geometry, material);
116
+ // Embers over the glow: drawn after it, whatever the sort says.
117
+ points.renderOrder = ring ? 3 : ember ? 2 : 1;
118
+ points.frustumCulled = false;
119
+ return { points, material, alpha, base: Float32Array.from(alphas), flash };
120
+ }
121
+ /**
122
+ * The stars' pixel scale for a canvas and a camera: pixels per unit of size
123
+ * at unit depth.
124
+ *
125
+ * @param heightPx - The canvas's height in device pixels.
126
+ * @param fovDeg - The camera's vertical field of view, in degrees.
127
+ * @returns The scale the star shader multiplies by.
128
+ */
129
+ export function starScale_of(heightPx, fovDeg) {
130
+ return heightPx / (2 * Math.tan((fovDeg * Math.PI) / 360));
131
+ }
132
+ //# sourceMappingURL=stars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stars.js","sourceRoot":"","sources":["../../src/draw/stars.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @file The drawing's clock: how a wave steps down a graph, how long a pulse takes, how long a replay rests.
3
+ *
4
+ * @module
5
+ */
6
+ /** Wave delay between one dependency tier firing and the next. */
7
+ export declare const WAVE_STEP_MS: number;
8
+ /** A pulse's trip along one tube; a live edge repeats it. */
9
+ export declare const PULSE_TRIP_MS: number;
10
+ /** The rest between two replays of a finished feed's run. */
11
+ export declare const REPLAY_REST_MS: number;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file The drawing's clock: how a wave steps down a graph, how long a pulse takes, how long a replay rests.
3
+ *
4
+ * @module
5
+ */
6
+ /** Wave delay between one dependency tier firing and the next. */
7
+ export const WAVE_STEP_MS = 450;
8
+ /** A pulse's trip along one tube; a live edge repeats it. */
9
+ export const PULSE_TRIP_MS = 1200;
10
+ /** The rest between two replays of a finished feed's run. */
11
+ export const REPLAY_REST_MS = 1800;
12
+ //# sourceMappingURL=timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/draw/timing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,YAAY,GAAW,GAAG,CAAC;AAExC,6DAA6D;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAW,IAAI,CAAC;AAE1C,6DAA6D;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAW,IAAI,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @file The tubes of every solid molecule, and the lamps its stages wear.
3
+ *
4
+ * A tube joins two solid spheres and carries a pulse from parent to child:
5
+ * into a live stage it streams, and the stage blinks with each pulse that
6
+ * lands; into a finished one it replays the run stage by stage in the order
7
+ * the stages ran, each stage dark until its pulse lands, flaring then and
8
+ * staying lit, the lot dimming before the next replay. A tube into a failed
9
+ * stage is red. A tube follows its spheres when one is pulled, and re-reads
10
+ * the states it carries when a stage starts or finishes.
11
+ *
12
+ * @module
13
+ */
14
+ import * as THREE from 'three';
15
+ import type { NodeState } from '../types/encoding.js';
16
+ import type { Palette } from './palette.js';
17
+ import { type TubeSpec } from './tubes.js';
18
+ /**
19
+ * One node as the tubes read it.
20
+ *
21
+ * @property position - Where it stands.
22
+ * @property radius - Its sphere's radius.
23
+ * @property parents - The nodes it hangs from.
24
+ * @property joins - The nodes it joins from another branch.
25
+ * @property state - What it is doing.
26
+ */
27
+ export interface TubeNode {
28
+ position: THREE.Vector3;
29
+ radius: number;
30
+ parents: ReadonlyArray<string>;
31
+ joins: ReadonlyArray<string>;
32
+ state: NodeState;
33
+ }
34
+ /**
35
+ * What holds a set of tubes: the field sets `tubes` to the mesh it draws
36
+ * for it, and draws them at the owner's `mix` (a molecule fading in).
37
+ */
38
+ export interface TubeOwner {
39
+ tubes: THREE.InstancedMesh | null;
40
+ mix: number;
41
+ }
42
+ /** What the field needs to know of the scene it draws in. */
43
+ export interface TubeFieldHost {
44
+ /** Where the tubes are drawn. */
45
+ parent: THREE.Object3D;
46
+ /** A node as the tubes read it, now. */
47
+ node(id: string): TubeNode | undefined;
48
+ /** A stage's sphere, for its lamp; undefined when it is not drawn solid. */
49
+ sphere(id: string): THREE.Mesh | undefined;
50
+ /** The colours, now. */
51
+ palette(): Palette;
52
+ }
53
+ /**
54
+ * A tube's pulse, by the state of the stage it runs into: it streams into a
55
+ * stage at work (1), replays into one that has run (2), rests otherwise (0).
56
+ *
57
+ * @param state - The stage's state.
58
+ * @returns The tube shader's mode.
59
+ */
60
+ export declare function tubeMode_of(state: NodeState): number;
61
+ /**
62
+ * The tubes of every solid molecule in a scene, and their lamps.
63
+ */
64
+ export declare class TubeField {
65
+ private readonly host;
66
+ private sets;
67
+ private materials;
68
+ private glows;
69
+ private stale;
70
+ private census;
71
+ /**
72
+ * @param host - The scene the field draws in.
73
+ */
74
+ constructor(host: TubeFieldHost);
75
+ /** How many tube materials are live (one per set and one for a census). */
76
+ materialCount(): number;
77
+ /** How many job-to-job tubes the census drew; 0 when it drew lines. */
78
+ censusCount(): number;
79
+ /** Whether any set of tubes runs through a node. */
80
+ holds(id: string): boolean;
81
+ /** A stage under a set of tubes started or finished: its tubes are re-read next frame. */
82
+ stale_mark(): void;
83
+ /**
84
+ * Joins a molecule's solid spheres with tubes.
85
+ *
86
+ * @param owner - What holds the set; its `tubes` becomes the mesh.
87
+ * @param entries - The molecule's nodes, children of its tubes.
88
+ * @param members - The nodes whose spheres are solid: a tube runs only between two.
89
+ */
90
+ build(owner: TubeOwner, entries: ReadonlyArray<string>, members: ReadonlySet<string>): void;
91
+ /**
92
+ * Draws a census's job-to-job tubes: one mesh for all of them.
93
+ *
94
+ * @param specs - The tubes.
95
+ * @param deepest - The deepest stage the wave reaches, for the replay's cycle.
96
+ */
97
+ census_build(specs: ReadonlyArray<TubeSpec>, deepest: number): void;
98
+ /**
99
+ * Moves the tubes' clock on, re-reads states that changed, and lights the lamps.
100
+ *
101
+ * @param now - This frame's time.
102
+ */
103
+ frame(now: number): void;
104
+ /**
105
+ * Brings every tube to the spheres it joins, where they stand now: a
106
+ * pulled node drags its tubes with it.
107
+ */
108
+ follow(): void;
109
+ /**
110
+ * Lets one set of tubes go: its molecule is stars again.
111
+ *
112
+ * @param mesh - The set's mesh.
113
+ */
114
+ release(mesh: THREE.InstancedMesh): void;
115
+ /** Forgets everything: the scene is being redrawn (its parent already cleared). */
116
+ clear(): void;
117
+ /** Re-reads every set of tubes from the states as they are now. */
118
+ private restatus;
119
+ /**
120
+ * Lights a replayed molecule's stages on its tubes' clock: every stage
121
+ * starts the cycle dimmed, flares as its pulse lands and stays lit, and
122
+ * the lot dims again as the rest runs out — then the run replays.
123
+ */
124
+ private lamps_animate;
125
+ /**
126
+ * Paints one stage's lamp: its colour from dark to lit, and — while a
127
+ * pulse has just landed — a flash toward white and a glow that blooms
128
+ * out and settles.
129
+ */
130
+ private lamp_paint;
131
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * @file The tubes of every solid molecule, and the lamps its stages wear.
3
+ *
4
+ * A tube joins two solid spheres and carries a pulse from parent to child:
5
+ * into a live stage it streams, and the stage blinks with each pulse that
6
+ * lands; into a finished one it replays the run stage by stage in the order
7
+ * the stages ran, each stage dark until its pulse lands, flaring then and
8
+ * staying lit, the lot dimming before the next replay. A tube into a failed
9
+ * stage is red. A tube follows its spheres when one is pulled, and re-reads
10
+ * the states it carries when a stage starts or finishes.
11
+ *
12
+ * @module
13
+ */
14
+ import * as THREE from 'three';
15
+ import { LAMP_BLOOM_MS, LAMP_BLOOM_REACH, LAMP_DIM, LAMP_FADE_MS, LAMP_WHITE } from './lamps.js';
16
+ import { nebulaTexture_get } from './nebula.js';
17
+ import { PULSE_TRIP_MS, REPLAY_REST_MS, WAVE_STEP_MS } from './timing.js';
18
+ import { TUBE_FRAGMENT, TUBE_VERTEX, tubeGeometry_get, tubeMesh_make } from './tubes.js';
19
+ /**
20
+ * A tube's pulse, by the state of the stage it runs into: it streams into a
21
+ * stage at work (1), replays into one that has run (2), rests otherwise (0).
22
+ *
23
+ * @param state - The stage's state.
24
+ * @returns The tube shader's mode.
25
+ */
26
+ export function tubeMode_of(state) {
27
+ if (state === 'live')
28
+ return 1;
29
+ if (state === 'done' || state === 'failed')
30
+ return 2;
31
+ return 0;
32
+ }
33
+ /**
34
+ * The tubes of every solid molecule in a scene, and their lamps.
35
+ */
36
+ export class TubeField {
37
+ host;
38
+ sets = [];
39
+ materials = [];
40
+ glows = new Map();
41
+ stale = false;
42
+ census = 0;
43
+ /**
44
+ * @param host - The scene the field draws in.
45
+ */
46
+ constructor(host) {
47
+ this.host = host;
48
+ }
49
+ /** How many tube materials are live (one per set and one for a census). */
50
+ materialCount() {
51
+ return this.materials.length;
52
+ }
53
+ /** How many job-to-job tubes the census drew; 0 when it drew lines. */
54
+ censusCount() {
55
+ return this.census;
56
+ }
57
+ /** Whether any set of tubes runs through a node. */
58
+ holds(id) {
59
+ return this.sets.some((set) => set.members.has(id));
60
+ }
61
+ /** A stage under a set of tubes started or finished: its tubes are re-read next frame. */
62
+ stale_mark() {
63
+ this.stale = true;
64
+ }
65
+ /**
66
+ * Joins a molecule's solid spheres with tubes.
67
+ *
68
+ * @param owner - What holds the set; its `tubes` becomes the mesh.
69
+ * @param entries - The molecule's nodes, children of its tubes.
70
+ * @param members - The nodes whose spheres are solid: a tube runs only between two.
71
+ */
72
+ build(owner, entries, members) {
73
+ const opacity = owner.mix;
74
+ const palette = this.host.palette();
75
+ const edges = [];
76
+ for (const id of entries) {
77
+ const child = this.host.node(id);
78
+ if (child === undefined)
79
+ continue;
80
+ for (const parentId of child.parents) {
81
+ const parent = this.host.node(parentId);
82
+ if (parent !== undefined && members.has(parentId))
83
+ edges.push({ fromId: parentId, from: parent, toId: id, to: child, join: false });
84
+ }
85
+ for (const parentId of child.joins) {
86
+ const parent = this.host.node(parentId);
87
+ if (parent !== undefined && members.has(parentId))
88
+ edges.push({ fromId: parentId, from: parent, toId: id, to: child, join: true });
89
+ }
90
+ }
91
+ if (edges.length === 0)
92
+ return;
93
+ // A stage's depth in its molecule: the wave reaches it that many steps in.
94
+ const depth = new Map();
95
+ const depth_of = (id, seen = new Set()) => {
96
+ const known = depth.get(id);
97
+ if (known !== undefined)
98
+ return known;
99
+ if (seen.has(id))
100
+ return 0;
101
+ seen.add(id);
102
+ const node = this.host.node(id);
103
+ const parents = node === undefined ? [] : [...node.parents, ...node.joins].filter((p) => members.has(p));
104
+ const d = parents.length === 0 ? 0 : 1 + Math.max(...parents.map((p) => depth_of(p, seen)));
105
+ depth.set(id, d);
106
+ return d;
107
+ };
108
+ const now = performance.now();
109
+ const colors = new Float32Array(edges.length * 3);
110
+ const modes = new Float32Array(edges.length);
111
+ const starts = new Float32Array(edges.length);
112
+ const bright = palette.edge.clone().lerp(new THREE.Color('#ffffff'), 0.45);
113
+ const mesh = new THREE.InstancedMesh(tubeGeometry_get(), new THREE.ShaderMaterial({
114
+ uniforms: { time: { value: now }, opacity: { value: opacity }, born: { value: now }, cycle: { value: 1 } },
115
+ vertexShader: TUBE_VERTEX,
116
+ fragmentShader: TUBE_FRAGMENT,
117
+ transparent: true,
118
+ }), edges.length);
119
+ const up = new THREE.Vector3(0, 1, 0);
120
+ const carrier = new THREE.Object3D();
121
+ let deepest = 0;
122
+ const set = [];
123
+ // A replayed stage lights when its pulse arrives: the root at once,
124
+ // every other stage as the first tube into it lands.
125
+ const lamps = new Map();
126
+ // A running stage blinks with the stream: lit each time a pulse lands,
127
+ // dark again before the next, until it is done.
128
+ const blinks = new Set();
129
+ edges.forEach(({ fromId, from, toId, to, join }, i) => {
130
+ const along = to.position.clone().sub(from.position);
131
+ const length = along.length();
132
+ // Thick enough to read as a tube, not a hairline, at the molecule's framing.
133
+ const width = Math.max(0.06, Math.min(from.radius, to.radius) * 0.34);
134
+ carrier.position.copy(from.position).addScaledVector(along, 0.5);
135
+ carrier.quaternion.setFromUnitVectors(up, length > 0 ? along.clone().divideScalar(length) : up);
136
+ carrier.scale.set(width, length, width);
137
+ carrier.updateMatrix();
138
+ mesh.setMatrixAt(i, carrier.matrix);
139
+ set.push({ from: fromId, to: toId, width });
140
+ const color = to.state === 'failed' ? palette.error : join ? palette.join : bright;
141
+ colors.set([color.r, color.g, color.b], i * 3);
142
+ modes[i] = tubeMode_of(to.state);
143
+ const d = depth_of(fromId);
144
+ deepest = Math.max(deepest, d);
145
+ starts[i] = d * WAVE_STEP_MS;
146
+ if (modes[i] === 1)
147
+ blinks.add(toId);
148
+ if (modes[i] === 2) {
149
+ const landed = starts[i] + PULSE_TRIP_MS / 2;
150
+ lamps.set(toId, Math.min(lamps.get(toId) ?? Infinity, landed));
151
+ if (d === 0)
152
+ lamps.set(fromId, 0);
153
+ }
154
+ });
155
+ mesh.instanceMatrix.needsUpdate = true;
156
+ // Instanced attributes ride a clone of the shared tube, so each
157
+ // molecule's tubes carry their own colours and pulses.
158
+ const geometry = tubeGeometry_get().clone();
159
+ geometry.setAttribute('aColor', new THREE.InstancedBufferAttribute(colors, 3));
160
+ geometry.setAttribute('aMode', new THREE.InstancedBufferAttribute(modes, 1));
161
+ geometry.setAttribute('aStart', new THREE.InstancedBufferAttribute(starts, 1));
162
+ mesh.geometry = geometry;
163
+ mesh.frustumCulled = false;
164
+ this.host.parent.add(mesh);
165
+ owner.tubes = mesh;
166
+ if (mesh.material instanceof THREE.ShaderMaterial) {
167
+ this.materials.push(mesh.material);
168
+ const cycle = mesh.material.uniforms['cycle'];
169
+ if (cycle !== undefined)
170
+ cycle.value = (deepest + 1) * WAVE_STEP_MS + PULSE_TRIP_MS / 2 + REPLAY_REST_MS;
171
+ }
172
+ this.sets.push({ mesh, edges: set, lamps, blinks, owner, entries, members });
173
+ }
174
+ /**
175
+ * Draws a census's job-to-job tubes: one mesh for all of them.
176
+ *
177
+ * @param specs - The tubes.
178
+ * @param deepest - The deepest stage the wave reaches, for the replay's cycle.
179
+ */
180
+ census_build(specs, deepest) {
181
+ if (specs.length === 0)
182
+ return;
183
+ const mesh = tubeMesh_make(specs, (deepest + 1) * WAVE_STEP_MS + PULSE_TRIP_MS / 2 + REPLAY_REST_MS);
184
+ this.host.parent.add(mesh);
185
+ if (mesh.material instanceof THREE.ShaderMaterial)
186
+ this.materials.push(mesh.material);
187
+ this.census = specs.length;
188
+ }
189
+ /**
190
+ * Moves the tubes' clock on, re-reads states that changed, and lights the lamps.
191
+ *
192
+ * @param now - This frame's time.
193
+ */
194
+ frame(now) {
195
+ if (this.materials.length === 0)
196
+ return;
197
+ for (const material of this.materials) {
198
+ const time = material.uniforms['time'];
199
+ if (time !== undefined)
200
+ time.value = now;
201
+ }
202
+ if (this.stale)
203
+ this.restatus();
204
+ this.lamps_animate(now);
205
+ }
206
+ /**
207
+ * Brings every tube to the spheres it joins, where they stand now: a
208
+ * pulled node drags its tubes with it.
209
+ */
210
+ follow() {
211
+ const up = new THREE.Vector3(0, 1, 0);
212
+ const carrier = new THREE.Object3D();
213
+ for (const { mesh, edges } of this.sets) {
214
+ edges.forEach(({ from, to, width }, i) => {
215
+ const a = this.host.sphere(from);
216
+ const b = this.host.sphere(to);
217
+ if (a === undefined || b === undefined)
218
+ return;
219
+ const along = b.position.clone().sub(a.position);
220
+ const length = along.length();
221
+ carrier.position.copy(a.position).addScaledVector(along, 0.5);
222
+ carrier.quaternion.setFromUnitVectors(up, length > 0 ? along.clone().divideScalar(length) : up);
223
+ carrier.scale.set(width, length, width);
224
+ carrier.updateMatrix();
225
+ mesh.setMatrixAt(i, carrier.matrix);
226
+ });
227
+ mesh.instanceMatrix.needsUpdate = true;
228
+ }
229
+ }
230
+ /**
231
+ * Lets one set of tubes go: its molecule is stars again.
232
+ *
233
+ * @param mesh - The set's mesh.
234
+ */
235
+ release(mesh) {
236
+ this.host.parent.remove(mesh);
237
+ this.sets = this.sets.filter((set) => set.mesh !== mesh);
238
+ mesh.geometry.dispose();
239
+ if (mesh.material instanceof THREE.ShaderMaterial) {
240
+ const material = mesh.material;
241
+ this.materials = this.materials.filter((m) => m !== material);
242
+ material.dispose();
243
+ }
244
+ }
245
+ /** Forgets everything: the scene is being redrawn (its parent already cleared). */
246
+ clear() {
247
+ this.materials = [];
248
+ this.census = 0;
249
+ this.sets = [];
250
+ for (const glow of this.glows.values())
251
+ glow.material.dispose();
252
+ this.glows = new Map();
253
+ }
254
+ /** Re-reads every set of tubes from the states as they are now. */
255
+ restatus() {
256
+ this.stale = false;
257
+ const stale = this.sets;
258
+ this.sets = [];
259
+ for (const set of stale) {
260
+ this.host.parent.remove(set.mesh);
261
+ set.mesh.geometry.dispose();
262
+ if (set.mesh.material instanceof THREE.ShaderMaterial) {
263
+ const material = set.mesh.material;
264
+ this.materials = this.materials.filter((m) => m !== material);
265
+ material.dispose();
266
+ }
267
+ set.owner.tubes = null;
268
+ this.build(set.owner, set.entries, set.members);
269
+ }
270
+ this.follow();
271
+ }
272
+ /**
273
+ * Lights a replayed molecule's stages on its tubes' clock: every stage
274
+ * starts the cycle dimmed, flares as its pulse lands and stays lit, and
275
+ * the lot dims again as the rest runs out — then the run replays.
276
+ */
277
+ lamps_animate(now) {
278
+ const shown = new Set();
279
+ for (const { mesh: tubes, lamps, blinks } of this.sets) {
280
+ if ((lamps.size === 0 && blinks.size === 0) || !(tubes.material instanceof THREE.ShaderMaterial))
281
+ continue;
282
+ const born = Number(tubes.material.uniforms['born']?.value ?? now);
283
+ const cycle = Number(tubes.material.uniforms['cycle']?.value ?? 1);
284
+ const local = (((now - born) % cycle) + cycle) % cycle;
285
+ // Out of the cycle's last stretch every lamp fades back down.
286
+ const fade = Math.min(1, Math.max(0, (cycle - local) / LAMP_FADE_MS));
287
+ for (const [id, landed] of lamps) {
288
+ const since = local - landed;
289
+ this.lamp_paint(id, since < 0 ? 0 : fade, since >= 0 && since < LAMP_BLOOM_MS ? since / LAMP_BLOOM_MS : -1, shown);
290
+ }
291
+ // The stream's pulse lands every trip; a running stage flares then
292
+ // and dies back toward dark.
293
+ const trip = ((now % PULSE_TRIP_MS) + PULSE_TRIP_MS) % PULSE_TRIP_MS;
294
+ for (const id of blinks) {
295
+ if (lamps.has(id))
296
+ continue;
297
+ const decay = Math.max(0, 1 - trip / (PULSE_TRIP_MS * 0.8));
298
+ this.lamp_paint(id, decay * decay, trip < LAMP_BLOOM_MS ? trip / LAMP_BLOOM_MS : -1, shown);
299
+ }
300
+ }
301
+ for (const [id, glow] of this.glows) {
302
+ if (shown.has(id))
303
+ continue;
304
+ glow.visible = false;
305
+ if (this.host.sphere(id) === undefined) {
306
+ this.host.parent.remove(glow);
307
+ glow.material.dispose();
308
+ this.glows.delete(id);
309
+ }
310
+ }
311
+ }
312
+ /**
313
+ * Paints one stage's lamp: its colour from dark to lit, and — while a
314
+ * pulse has just landed — a flash toward white and a glow that blooms
315
+ * out and settles.
316
+ */
317
+ lamp_paint(id, lit, bloom, shown) {
318
+ const sphere = this.host.sphere(id);
319
+ if (sphere === undefined || !(sphere.material instanceof THREE.MeshStandardMaterial))
320
+ return;
321
+ let base = sphere.userData['lampBase'];
322
+ if (!(base instanceof THREE.Color)) {
323
+ base = sphere.material.color.clone();
324
+ sphere.userData['lampBase'] = base;
325
+ }
326
+ const flash = bloom < 0 ? 0 : (1 - bloom) ** 2;
327
+ sphere.material.color
328
+ .copy(base)
329
+ .multiplyScalar(LAMP_DIM + (1 - LAMP_DIM) * Math.max(lit, flash))
330
+ .lerp(LAMP_WHITE, Math.min(1, 0.22 * lit + 0.7 * flash));
331
+ if (bloom < 0)
332
+ return;
333
+ let glow = this.glows.get(id);
334
+ if (glow === undefined) {
335
+ glow = new THREE.Sprite(new THREE.SpriteMaterial({
336
+ map: nebulaTexture_get(), color: base.clone().lerp(LAMP_WHITE, 0.35),
337
+ transparent: true, blending: THREE.AdditiveBlending, depthWrite: false,
338
+ }));
339
+ this.glows.set(id, glow);
340
+ this.host.parent.add(glow);
341
+ }
342
+ const radius = sphere.geometry.parameters?.radius ?? 0.55;
343
+ // Out fast, then settle: the glow swells to its reach and fades.
344
+ const swell = 1 - (1 - bloom) ** 3;
345
+ glow.position.copy(sphere.position);
346
+ glow.scale.setScalar(radius * (2 + LAMP_BLOOM_REACH * swell) * sphere.scale.x);
347
+ glow.material.opacity = 0.9 * (1 - bloom) ** 1.5;
348
+ glow.visible = true;
349
+ shown.add(id);
350
+ }
351
+ }
352
+ //# sourceMappingURL=tubeField.js.map