@juspay/svelte-ui-components 4.33.1 → 4.33.3
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/dist/ThinkingOrb/orb/modes/form.js +51 -13
- package/dist/ThinkingOrb/orb/modes/network.d.ts +5 -1
- package/dist/ThinkingOrb/orb/modes/network.js +93 -46
- package/dist/ThinkingOrb/orb/modes/sphere.js +147 -58
- package/dist/ThinkingOrb/orb/space.d.ts +7 -2
- package/dist/ThinkingOrb/orb/space.js +3 -1
- package/dist-wc/index.js +145 -84
- package/package.json +1 -1
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
* split keeps "the shape's own motion" and "the camera's fixed angle on it"
|
|
12
12
|
* from tangling together in the per-dot math.
|
|
13
13
|
*/
|
|
14
|
-
import { easeInOutCubic, hashUnit, modeSizing, project, rotateX, rotateY, rotateZ } from '../space';
|
|
14
|
+
import { easeInOutCubic, hashUnit, modeSizing, project, rotateX, rotateY, rotateZ, sampleSphere } from '../space';
|
|
15
15
|
const TWO_PI = Math.PI * 2;
|
|
16
16
|
/** The shared "reads as a form inside a square" contract: radius as a share of `size`. */
|
|
17
17
|
const FORM_RADIUS_RATIO = 0.4;
|
|
18
18
|
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
19
|
+
/** How far toward transparent the farthest dot's ink goes. `paintFrame` multiplies (1 - ink) with
|
|
20
|
+
* `alpha`, so ink reaching 1 would erase the far side of a ring or ribbon however high its alpha. */
|
|
21
|
+
const FAR_INK = 0.6;
|
|
19
22
|
/** Nearer dots read as more saturated tint; `paintFrame` turns low ink into more of `tint`. */
|
|
20
|
-
const depthToInk = (depth) => clamp01(1 - depth);
|
|
23
|
+
const depthToInk = (depth) => clamp01((1 - depth) * FAR_INK);
|
|
21
24
|
/** Farther dots stay at partial opacity rather than fading to nothing, so no instant reads blank. */
|
|
22
25
|
const depthToAlpha = (depth) => clamp01(0.5 + depth * 0.5);
|
|
23
26
|
/** Nearer dots draw a touch larger, reinforcing the depth `paintFrame`'s draw order already implies. */
|
|
@@ -40,10 +43,17 @@ const place = (point, spinAngle, tiltAngle, originPx, worldRadius, baseRadius, s
|
|
|
40
43
|
};
|
|
41
44
|
const EMPTY_STROKES = [];
|
|
42
45
|
// --- composing ---------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
/** Dots along each thread at 64px; `countAlongPath` keeps their spacing as the canvas shrinks. */
|
|
47
|
+
const RIBBON_PER_THREAD_BASE = 36;
|
|
48
|
+
/** Reference parity (BZ-6466 follow-up): upstream thinking-orbs' `ribbon` mode resolves to a
|
|
49
|
+
* ~0.9-1.1px base dot radius at 64px (its BASE_PROFILES rBase 1.1, x0.85 for this preset's `size`
|
|
50
|
+
* multiplier). This used to carry 2.2px, exactly the radius that fused composing's dots into a
|
|
51
|
+
* solid corrugated band instead of the reference's fine, individually-visible beads. */
|
|
52
|
+
const RIBBON_BASE_DOT_RADIUS = 1.2;
|
|
53
|
+
/** Fewer, narrower threads than a full band: at 7 threads across 0.4 rad the 64px ribbon covered
|
|
54
|
+
* most of the sphere and read as a filled ball rather than a strip of cloth. */
|
|
55
|
+
const RIBBON_THREADS = { 64: 5, 32: 4, 20: 3 };
|
|
56
|
+
const RIBBON_HALF_WIDTH = 0.3;
|
|
47
57
|
/** The ribbon's own great circle sits tilted off the spin axis by this much,
|
|
48
58
|
* so spinning genuinely sweeps its width through the frame -- a band that
|
|
49
59
|
* happened to lie flat on the equator would look identical at every spin
|
|
@@ -51,7 +61,9 @@ const RIBBON_HALF_WIDTH = 0.4;
|
|
|
51
61
|
const RIBBON_AXIS_TILT = 0.68;
|
|
52
62
|
const RIBBON_RIPPLE_WAVES = 3;
|
|
53
63
|
const RIBBON_RIPPLE_SPEED = 1.15;
|
|
54
|
-
|
|
64
|
+
/** Kept under RIBBON_HALF_WIDTH: a ripple wider than the band itself swings the ribbon toward
|
|
65
|
+
* the pole and folds it over itself, which reads as a knot rather than cloth. */
|
|
66
|
+
const RIBBON_RIPPLE_AMPLITUDE = 0.18;
|
|
55
67
|
const RIBBON_THREAD_JITTER = 0.1;
|
|
56
68
|
const RIBBON_SPIN_SPEED = 0.48;
|
|
57
69
|
const RIBBON_CAMERA_TILT = 0.45;
|
|
@@ -60,6 +72,16 @@ const RIBBON_CAMERA_TILT = 0.45;
|
|
|
60
72
|
* combined ink/alpha/radius fade would collapse an edge row to a speck. */
|
|
61
73
|
const RIBBON_EDGE_ALPHA_FLOOR = 0.5;
|
|
62
74
|
const RIBBON_EDGE_INK_LIFT = 0.22;
|
|
75
|
+
/** Reference parity (BZ-6466 follow-up): upstream's `ribbon` mode fills the sphere behind the band
|
|
76
|
+
* with a faint, fully static "ghost" texture (150 dots at 64px, this preset's `spin: 0` freezing
|
|
77
|
+
* even the shared camera precession that would otherwise turn it). Ours had no such backing layer,
|
|
78
|
+
* so the ribbon read as a thin strip floating in empty space rather than cloth wrapped on a solid
|
|
79
|
+
* form -- the ribbon's own turn was the only motion on screen, with nothing to anchor it against. */
|
|
80
|
+
const RIBBON_GHOST_BASE_COUNT = 110;
|
|
81
|
+
const RIBBON_GHOST_RADIUS_PX = 0.5;
|
|
82
|
+
const RIBBON_GHOST_INK = 0.3;
|
|
83
|
+
const RIBBON_GHOST_ALPHA_BASE = 0.16;
|
|
84
|
+
const RIBBON_GHOST_ALPHA_DEPTH = 0.22;
|
|
63
85
|
/**
|
|
64
86
|
* A ribbon of dots looped once around the sphere on its own tilted great
|
|
65
87
|
* circle -- distinct from the spin axis -- rippling along its length like a
|
|
@@ -70,9 +92,22 @@ export const composing = (t, ctx) => {
|
|
|
70
92
|
const worldRadius = ctx.size * FORM_RADIUS_RATIO;
|
|
71
93
|
const originPx = ctx.size / 2;
|
|
72
94
|
const threads = RIBBON_THREADS[ctx.size];
|
|
73
|
-
const perThread =
|
|
95
|
+
const perThread = sizing.countAlongPath(RIBBON_PER_THREAD_BASE);
|
|
74
96
|
const total = threads * perThread;
|
|
75
|
-
const
|
|
97
|
+
const ghostCount = Math.max(20, sizing.count(RIBBON_GHOST_BASE_COUNT));
|
|
98
|
+
const dots = Array.from({ length: ghostCount }, (_unused, i) => {
|
|
99
|
+
const point = sampleSphere(i, ghostCount, worldRadius);
|
|
100
|
+
const tilted = rotateX(point, RIBBON_CAMERA_TILT);
|
|
101
|
+
const projected = project(tilted, originPx, worldRadius);
|
|
102
|
+
return {
|
|
103
|
+
x: projected.x,
|
|
104
|
+
y: projected.y,
|
|
105
|
+
depth: projected.depth,
|
|
106
|
+
radius: sizing.radius(RIBBON_GHOST_RADIUS_PX),
|
|
107
|
+
ink: RIBBON_GHOST_INK,
|
|
108
|
+
alpha: clamp01(RIBBON_GHOST_ALPHA_BASE + RIBBON_GHOST_ALPHA_DEPTH * projected.depth)
|
|
109
|
+
};
|
|
110
|
+
});
|
|
76
111
|
for (let index = 0; index < total; index += 1) {
|
|
77
112
|
const thread = index % threads;
|
|
78
113
|
const step = Math.floor(index / threads);
|
|
@@ -102,7 +137,9 @@ export const composing = (t, ctx) => {
|
|
|
102
137
|
return { dots, strokes: EMPTY_STROKES };
|
|
103
138
|
};
|
|
104
139
|
// --- breathing -----------------------------------------------------------
|
|
105
|
-
|
|
140
|
+
/** Dots around the ring at 64px. A ring's length grows with `size`, not its area, so this goes
|
|
141
|
+
* through `countAlongPath`: 210 area-scaled dots sat 0.65px apart at 64px and fused into a line. */
|
|
142
|
+
const BREATHING_BASE_COUNT = 32;
|
|
106
143
|
const BREATHING_BASE_DOT_RADIUS = 1.7;
|
|
107
144
|
/** Sits below composing/shaping's ~0.4: at the peak of its breath this ring
|
|
108
145
|
* already swells by its amplitude plus its shape wobble below, so a resting
|
|
@@ -125,7 +162,7 @@ export const breathing = (t, ctx) => {
|
|
|
125
162
|
const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
|
|
126
163
|
const worldRadius = ctx.size * BREATHING_WORLD_RADIUS_RATIO;
|
|
127
164
|
const originPx = ctx.size / 2;
|
|
128
|
-
const count = sizing.
|
|
165
|
+
const count = sizing.countAlongPath(BREATHING_BASE_COUNT);
|
|
129
166
|
const breathe = Math.sin(t * BREATHING_SPEED);
|
|
130
167
|
const dots = [];
|
|
131
168
|
for (let index = 0; index < count; index += 1) {
|
|
@@ -144,7 +181,8 @@ export const breathing = (t, ctx) => {
|
|
|
144
181
|
return { dots, strokes: EMPTY_STROKES };
|
|
145
182
|
};
|
|
146
183
|
// --- shaping ---------------------------------------------------------------
|
|
147
|
-
|
|
184
|
+
/** Dots around the outline at 64px, through `countAlongPath` for the same reason as breathing. */
|
|
185
|
+
const SHAPING_BASE_COUNT = 36;
|
|
148
186
|
const SHAPING_BASE_DOT_RADIUS = 1.7;
|
|
149
187
|
const SHAPING_CYCLE_SECONDS = 12;
|
|
150
188
|
// One full turn per morph cycle, so the shape and its orientation both land
|
|
@@ -169,7 +207,7 @@ export const shaping = (t, ctx) => {
|
|
|
169
207
|
const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
|
|
170
208
|
const worldRadius = ctx.size * FORM_RADIUS_RATIO;
|
|
171
209
|
const originPx = ctx.size / 2;
|
|
172
|
-
const count = sizing.
|
|
210
|
+
const count = sizing.countAlongPath(SHAPING_BASE_COUNT);
|
|
173
211
|
const cyclePos = (((t / SHAPING_CYCLE_SECONDS) % 1) + 1) % 1;
|
|
174
212
|
const phaseFloat = cyclePos * 3;
|
|
175
213
|
const phaseIndex = Math.min(2, Math.floor(phaseFloat));
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* projection setup below.
|
|
7
7
|
*/
|
|
8
8
|
import type { ModeFn } from '../types';
|
|
9
|
+
import type { ModeSizing } from '../space';
|
|
9
10
|
/** A few nodes on a sphere; lines to near neighbours carry small travelling pulses. */
|
|
10
11
|
export declare const connecting: ModeFn;
|
|
11
|
-
/**
|
|
12
|
+
/** Exported so tests that index `weaving`'s strand dots can reproduce and slice off this count,
|
|
13
|
+
* rather than duplicating the formula as a hardcoded literal. */
|
|
14
|
+
export declare const weavingGhostCount: (sizing: ModeSizing) => number;
|
|
15
|
+
/** Three closed strands wrapped around a sphere, crossing over and under each other as they turn. */
|
|
12
16
|
export declare const weaving: ModeFn;
|
|
@@ -25,11 +25,14 @@ const squaredDistance = (a, b) => {
|
|
|
25
25
|
// connecting -- a few nodes on a sphere, linked to their nearest neighbours,
|
|
26
26
|
// with small bright pulses running along the links.
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
28
|
+
/** A few nodes, not a molecule: at 16 nodes x 3 neighbours the 64px graph carried ~24 edges, each
|
|
29
|
+
* with its own pulse, and the pulses were lost among the nodes. */
|
|
30
|
+
const NODE_BASE_COUNT = 10;
|
|
29
31
|
const MIN_NODE_COUNT = 6;
|
|
30
|
-
const NEIGHBOR_COUNT =
|
|
32
|
+
const NEIGHBOR_COUNT = 2;
|
|
31
33
|
const NODE_BASE_RADIUS = 3.4;
|
|
32
|
-
|
|
34
|
+
/** Well under a node's radius, so a pulse reads as something travelling a link, not another node. */
|
|
35
|
+
const PULSE_BASE_RADIUS = 1.4;
|
|
33
36
|
const EDGE_BASE_WIDTH = 1.1;
|
|
34
37
|
const NODE_ROTATE_SPEED = 0.35;
|
|
35
38
|
const NODE_TILT = 0.45;
|
|
@@ -104,63 +107,90 @@ export const connecting = (t, ctx) => {
|
|
|
104
107
|
return { dots: [...nodeDots, ...pulses], strokes };
|
|
105
108
|
};
|
|
106
109
|
// ---------------------------------------------------------------------------
|
|
107
|
-
// weaving -- three strands
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
+
// weaving -- three closed strands wrapped around a sphere like the windings on
|
|
111
|
+
// a ball of yarn; the depth sort in `paintFrame` is what makes them cross over
|
|
112
|
+
// and under each other.
|
|
110
113
|
// ---------------------------------------------------------------------------
|
|
111
114
|
const STRAND_COUNT = 3;
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
/**
|
|
115
|
-
*
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
/** Points around each closed strand at 64px, through `countAlongPath`. */
|
|
116
|
+
const STRAND_POINTS_BASE_COUNT = 40;
|
|
117
|
+
/** Enough points that a strand still draws as a curve at 20px; at 10 its segments were long
|
|
118
|
+
* enough to show as straight edges and the three strands drew an angular cage. */
|
|
119
|
+
const MIN_STRAND_POINTS = 16;
|
|
120
|
+
/** Each strand is a great circle tilted this far off the horizontal, and the three are turned
|
|
121
|
+
* 120 degrees apart about the vertical axis, so every pair crosses twice per turn. A pole-to-pole
|
|
122
|
+
* spiral kept projecting two strands onto nearly the same path. */
|
|
123
|
+
const STRAND_INCLINATION = 0.6;
|
|
124
|
+
/** A slow wave running along each strand, so the three read as threads being worked rather than
|
|
125
|
+
* three rigid hoops. Small, so the wave never folds a strand across its neighbour's path. */
|
|
126
|
+
const STRAND_WAVE_COUNT = 3;
|
|
127
|
+
const STRAND_WAVE_AMPLITUDE = 0.12;
|
|
128
|
+
const STRAND_WAVE_SPEED = 0.9;
|
|
118
129
|
const STRAND_ROTATE_SPEED = 0.3;
|
|
119
|
-
|
|
130
|
+
/** How far the camera looks down on the sphere: 1.1 rad, about 63 degrees. A ring turns edge-on,
|
|
131
|
+
* and draws as a straight bar, when its axis is square to the line of sight. Each ring's axis sits
|
|
132
|
+
* STRAND_INCLINATION (about 34 degrees) off vertical, and this tilt leaves the vertical about
|
|
133
|
+
* 90 - 63 = 27 degrees off the line of sight, so a ring's axis is never more than 34 + 27 = 61
|
|
134
|
+
* degrees off it. No spin angle, and so no paused frame, draws a ring flatter than about
|
|
135
|
+
* cos 61 = 48% of its width. */
|
|
136
|
+
const STRAND_TILT = 1.1;
|
|
120
137
|
const STRAND_DOT_BASE_RADIUS = 1.6;
|
|
121
138
|
const STRAND_STROKE_BASE_WIDTH = 1.4;
|
|
122
|
-
/**
|
|
123
|
-
*
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
/**
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*
|
|
132
|
-
|
|
139
|
+
/** At the small sizes the strand lines carry the motif and the beads only clutter it, so lines
|
|
140
|
+
* thicken and beads shrink as the canvas does. */
|
|
141
|
+
const STRAND_STROKE_SIZE_FACTOR = { 64: 1, 32: 1.2, 20: 1.5 };
|
|
142
|
+
const STRAND_DOT_SIZE_FACTOR = { 64: 1, 32: 0.85, 20: 0.7 };
|
|
143
|
+
/** Depth still dims a strand's far side, but only this far, so no stretch of it disappears. */
|
|
144
|
+
const STRAND_DEPTH_INK = 0.45;
|
|
145
|
+
/** A slight per-strand shade; the paths and their over/under crossings are what separate them. */
|
|
146
|
+
const STRAND_SHADE_STEP = 0.1;
|
|
147
|
+
/** Reference parity (BZ-6466 follow-up): upstream's `braid` mode fills the sphere behind the
|
|
148
|
+
* strands with a faint, static-in-object-space "ghost" texture (150 dots at 64px) that shares the
|
|
149
|
+
* strands' own turn, so the strands read as plaiting round a solid ball rather than floating loops
|
|
150
|
+
* in empty space. Ours had no such backing layer. Always appended after every strand's dots, so
|
|
151
|
+
* code and tests that index `dots` by strand can slice it off by a known, reproducible count. */
|
|
152
|
+
const STRAND_GHOST_BASE_COUNT = 110;
|
|
153
|
+
const STRAND_GHOST_RADIUS_PX = 0.5;
|
|
154
|
+
const STRAND_GHOST_INK = 0.3;
|
|
155
|
+
/** With STRAND_GHOST_INK 0.3, `inkAlpha` = (1-ink)*alpha = 0.7*alpha, so this needs to clear
|
|
156
|
+
* 0.1/0.7 for the "nothing paints below 10% opacity" contract even on a ghost dot's far side. */
|
|
157
|
+
const STRAND_GHOST_ALPHA_BASE = 0.16;
|
|
158
|
+
const STRAND_GHOST_ALPHA_DEPTH = 0.22;
|
|
159
|
+
/** Exported so tests that index `weaving`'s strand dots can reproduce and slice off this count,
|
|
160
|
+
* rather than duplicating the formula as a hardcoded literal. */
|
|
161
|
+
export const weavingGhostCount = (sizing) => Math.max(20, sizing.count(STRAND_GHOST_BASE_COUNT));
|
|
133
162
|
const strandBaseAngle = (strand) => (strand / STRAND_COUNT) * Math.PI * 2;
|
|
134
163
|
/**
|
|
135
|
-
* The `pointIndex`-th of `pointsPerStrand` points
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* which is what keeps every point exactly on the sphere at every instant.
|
|
164
|
+
* The `pointIndex`-th of `pointsPerStrand` points around closed `strand`: a
|
|
165
|
+
* great circle (the first and last points meet) with a small wave in latitude
|
|
166
|
+
* travelling along it, inclined by `STRAND_INCLINATION`, turned to its own
|
|
167
|
+
* 120-degree slot and spun with the rest. Latitude and longitude keep every
|
|
168
|
+
* point exactly on the sphere, whatever the wave does.
|
|
141
169
|
*/
|
|
142
170
|
const strandPoint = (strand, pointIndex, pointsPerStrand, sphereRadius, t) => {
|
|
143
|
-
const u = pointIndex / (pointsPerStrand - 1);
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
const u = (pointIndex / (pointsPerStrand - 1)) * Math.PI * 2;
|
|
172
|
+
const latitude = STRAND_WAVE_AMPLITUDE *
|
|
173
|
+
Math.sin(u * STRAND_WAVE_COUNT + t * STRAND_WAVE_SPEED + strandBaseAngle(strand));
|
|
174
|
+
const onCircle = {
|
|
175
|
+
x: sphereRadius * Math.cos(latitude) * Math.cos(u),
|
|
176
|
+
y: sphereRadius * Math.sin(latitude),
|
|
177
|
+
z: sphereRadius * Math.cos(latitude) * Math.sin(u)
|
|
178
|
+
};
|
|
179
|
+
const inclined = rotateX(onCircle, STRAND_INCLINATION);
|
|
180
|
+
const turned = rotateY(inclined, strandBaseAngle(strand) + t * STRAND_ROTATE_SPEED);
|
|
181
|
+
return rotateX(turned, STRAND_TILT);
|
|
152
182
|
};
|
|
153
|
-
/** Three strands wrapped around a sphere, crossing
|
|
183
|
+
/** Three closed strands wrapped around a sphere, crossing over and under each other as they turn. */
|
|
154
184
|
export const weaving = (t, ctx) => {
|
|
155
185
|
const { sizing, originPx, sphereRadius } = sphereGeometry(ctx);
|
|
156
|
-
const pointsPerStrand = Math.max(MIN_STRAND_POINTS, sizing.
|
|
186
|
+
const pointsPerStrand = Math.max(MIN_STRAND_POINTS, sizing.countAlongPath(STRAND_POINTS_BASE_COUNT));
|
|
157
187
|
const strands = Array.from({ length: STRAND_COUNT }, (_, strand) => Array.from({ length: pointsPerStrand }, (_, pointIndex) => project(strandPoint(strand, pointIndex, pointsPerStrand, sphereRadius, t), originPx, sphereRadius)));
|
|
158
188
|
const dots = strands.flatMap((points, strand) => points.map((p) => ({
|
|
159
189
|
x: p.x,
|
|
160
190
|
y: p.y,
|
|
161
191
|
depth: p.depth,
|
|
162
|
-
radius: sizing.radius(STRAND_DOT_BASE_RADIUS),
|
|
163
|
-
ink: clamp01(1 - p.depth *
|
|
192
|
+
radius: sizing.radius(STRAND_DOT_BASE_RADIUS) * STRAND_DOT_SIZE_FACTOR[ctx.size],
|
|
193
|
+
ink: clamp01((1 - p.depth) * STRAND_DEPTH_INK + strand * STRAND_SHADE_STEP),
|
|
164
194
|
alpha: 0.55 + p.depth * 0.4
|
|
165
195
|
})));
|
|
166
196
|
const strokes = strands.flatMap((points, strand) => points.slice(1).map((p, i) => {
|
|
@@ -172,10 +202,27 @@ export const weaving = (t, ctx) => {
|
|
|
172
202
|
x2: p.x,
|
|
173
203
|
y2: p.y,
|
|
174
204
|
depth,
|
|
175
|
-
width: sizing.radius(STRAND_STROKE_BASE_WIDTH),
|
|
176
|
-
ink: clamp01(1 - depth *
|
|
205
|
+
width: sizing.radius(STRAND_STROKE_BASE_WIDTH) * STRAND_STROKE_SIZE_FACTOR[ctx.size],
|
|
206
|
+
ink: clamp01((1 - depth) * STRAND_DEPTH_INK + strand * STRAND_SHADE_STEP),
|
|
177
207
|
alpha: 0.6 + depth * 0.35
|
|
178
208
|
};
|
|
179
209
|
}));
|
|
180
|
-
|
|
210
|
+
// Shares the strands' own spin+tilt (not their per-strand inclination/phase, which is strand
|
|
211
|
+
// geometry, not camera) so the backdrop turns together with what it is behind.
|
|
212
|
+
const ghostCount = weavingGhostCount(sizing);
|
|
213
|
+
const ghosts = Array.from({ length: ghostCount }, (_unused, i) => {
|
|
214
|
+
const point = sampleSphere(i, ghostCount, sphereRadius);
|
|
215
|
+
const spun = rotateY(point, t * STRAND_ROTATE_SPEED);
|
|
216
|
+
const tilted = rotateX(spun, STRAND_TILT);
|
|
217
|
+
const projected = project(tilted, originPx, sphereRadius);
|
|
218
|
+
return {
|
|
219
|
+
x: projected.x,
|
|
220
|
+
y: projected.y,
|
|
221
|
+
depth: projected.depth,
|
|
222
|
+
radius: sizing.radius(STRAND_GHOST_RADIUS_PX),
|
|
223
|
+
ink: STRAND_GHOST_INK,
|
|
224
|
+
alpha: clamp01(STRAND_GHOST_ALPHA_BASE + STRAND_GHOST_ALPHA_DEPTH * projected.depth)
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
return { dots: [...dots, ...ghosts], strokes };
|
|
181
228
|
};
|
|
@@ -44,23 +44,33 @@ const toStroke = (from, to, width, ink, alpha) => ({
|
|
|
44
44
|
/** Particles travel in short trailing arcs ("trains") rather than scattering
|
|
45
45
|
* independently around their orbit -- a lone random phase per particle reads
|
|
46
46
|
* as noise once density thins the count out at 32/20px, while a handful of
|
|
47
|
-
* particles held close together always reads as motion along a path.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
* particles held close together always reads as motion along a path.
|
|
48
|
+
*
|
|
49
|
+
* Reference parity (BZ-6466 follow-up): the upstream thinking-orbs `orbits`
|
|
50
|
+
* mode this was ported from draws 12 orbits x 3 particles = 36 marks at
|
|
51
|
+
* 64px, radius ~1.2px, and reads as a sparse field of distinct particles.
|
|
52
|
+
* The previous tuning here (22 trains x 5-long, radius 2.2px = 110 marks)
|
|
53
|
+
* packed 3x the marks at ~1.7x the radius into the same arcs, so consecutive
|
|
54
|
+
* dots overlapped their own diameter and the whole thing fused into a
|
|
55
|
+
* blurred scribble instead of scattered points. */
|
|
56
|
+
const WORKING_TRAIN_BASE_COUNT = 12;
|
|
57
|
+
/** Keeps at least this many short arcs on screen at 64px (32/20px use WORKING_SMALL_LAYOUT). The
|
|
58
|
+
* area scaler alone already clears it at the default density; it only bites when density is low. */
|
|
59
|
+
const WORKING_MIN_TRAINS_64 = 3;
|
|
60
|
+
const WORKING_TRAIN_LENGTH = 3;
|
|
59
61
|
/** How far apart, in radians, a train's particles trail behind its lead --
|
|
60
62
|
* wide enough to read as a short curved arc, short enough to stay a cluster
|
|
61
63
|
* rather than smearing back into the full-orbit scatter this replaces. */
|
|
62
64
|
const WORKING_TRAIN_ARC_SPAN = 0.5;
|
|
63
|
-
|
|
65
|
+
/** Reference's `orbits` mode draws particles at ~1.2px on a 64px canvas but scales them back UP
|
|
66
|
+
* at smaller sizes (2.4x at 20px) rather than shrinking further, so they stay legible once there's
|
|
67
|
+
* less room for arc spacing to keep them apart. `sizing.radius`'s sub-linear shrink-with-a-floor
|
|
68
|
+
* goes the other way, so the two sizes need their own base rather than one shared constant. */
|
|
69
|
+
const WORKING_BASE_RADIUS_PX = 1.3;
|
|
70
|
+
/** 32/20px (WORKING_SMALL_LAYOUT): kept at this mode's original, pre-reference-parity radius --
|
|
71
|
+
* these sizes weren't the fused-blur regression 64px was, and shrinking to match 64px's new,
|
|
72
|
+
* smaller radius here would drop dots below a legible stroke width. */
|
|
73
|
+
const WORKING_SMALL_BASE_RADIUS_PX = 2.2;
|
|
64
74
|
const WORKING_ORBIT_COUNT = 6;
|
|
65
75
|
const WORKING_ANGULAR_SPEED = 1.1;
|
|
66
76
|
/** How far an orbit's `weight` can push its dots' ink away from the shared
|
|
@@ -72,6 +82,17 @@ const WORKING_ORBIT_ALPHA_FLOOR = 0.65;
|
|
|
72
82
|
/** A train's trailing-most particle never fades past this floor, so the arc's
|
|
73
83
|
* tail stays legible instead of thinning away to nothing. */
|
|
74
84
|
const WORKING_TRAIL_ALPHA_FLOOR = 0.35;
|
|
85
|
+
/** Reference parity (BZ-6466 follow-up): upstream's `orbits` mode traces each orbit's full circle
|
|
86
|
+
* as a faint, static "ghost" ring of many dots (40 per orbit at 64px) UNDER the few bright
|
|
87
|
+
* travelling particles -- about 13x as many ghost dots as particles, so the ring lattice anchors
|
|
88
|
+
* the silhouette and the particles read as a sparkle on top of it. Ours had no such layer: every
|
|
89
|
+
* visible dot was one of the sweeping particles, so with only 6-22 of them the whole silhouette
|
|
90
|
+
* reshaped every frame instead of holding a roughly stable shape -- the actual "fluidity" gap a
|
|
91
|
+
* static/moving frame comparison alone doesn't show. */
|
|
92
|
+
const WORKING_GHOST_DOTS_PER_ORBIT = 40;
|
|
93
|
+
const WORKING_GHOST_RADIUS_PX = 0.7;
|
|
94
|
+
const WORKING_GHOST_INK = 0.4;
|
|
95
|
+
const WORKING_GHOST_ALPHA = 0.5;
|
|
75
96
|
/** Every particle's own orbital plane, radius, pace and shading weight, keyed by its orbit. */
|
|
76
97
|
const workingOrbit = (orbit) => {
|
|
77
98
|
const seed = orbit * 31 + 7;
|
|
@@ -83,19 +104,60 @@ const workingOrbit = (orbit) => {
|
|
|
83
104
|
weight: 0.35 + 0.65 * hashUnit(seed + 15)
|
|
84
105
|
};
|
|
85
106
|
};
|
|
107
|
+
/** At 32/20px six randomly tilted orbits of short arcs collapse into one busy cluster: each arc is
|
|
108
|
+
* only a few px long and the orbits overlap. These sizes instead spread a few orbits evenly around
|
|
109
|
+
* the view axis (like an atom symbol) and give each train a long arc, so every train draws a
|
|
110
|
+
* visible curve and the separate orbits stay readable. */
|
|
111
|
+
const WORKING_SMALL_LAYOUT = {
|
|
112
|
+
// Shorter trains than before (was 6/7): at the smaller shared radius above, a longer train's
|
|
113
|
+
// dots sat closer together than their own diameter and fused the same way 64px did.
|
|
114
|
+
32: { orbits: 4, trainLength: 4, arcSpan: 0.75 },
|
|
115
|
+
20: { orbits: 3, trainLength: 4, arcSpan: 0.9 }
|
|
116
|
+
};
|
|
117
|
+
/** How far each small-size orbit tips out of edge-on. Its projection is an ellipse sin(tilt) as
|
|
118
|
+
* tall as it is wide; near 1 rad every orbit projects to nearly the same circle and rolling it
|
|
119
|
+
* about the view axis changes nothing, so the orbits merged into one ring. */
|
|
120
|
+
const WORKING_SMALL_ORBIT_TILT = 0.5;
|
|
121
|
+
/** At 32/20px each orbit's path is drawn as a faint thin ellipse under its trains: a 20px train
|
|
122
|
+
* alone is too short to show which ellipse it is travelling, so without the path the trains read
|
|
123
|
+
* as a pinwheel rather than as particles circling several tilted orbits. */
|
|
124
|
+
const WORKING_PATH_SEGMENTS = 24;
|
|
125
|
+
const WORKING_PATH_WIDTH_PX = 0.8;
|
|
126
|
+
const WORKING_PATH_INK = 0.5;
|
|
127
|
+
const WORKING_PATH_ALPHA = 0.45;
|
|
128
|
+
const WORKING_SMALL_ORBIT_RADIUS = 0.85;
|
|
129
|
+
const WORKING_SMALL_SPEEDS = [0.8, 1.15, 1.45, 1];
|
|
130
|
+
/** One orbit of the small-size layout: shared tilt, rolled evenly around the view axis. */
|
|
131
|
+
const workingSmallOrbit = (orbit, orbits) => ({
|
|
132
|
+
...workingOrbit(orbit),
|
|
133
|
+
tilt: WORKING_SMALL_ORBIT_TILT,
|
|
134
|
+
roll: (orbit / orbits) * Math.PI,
|
|
135
|
+
orbitRadiusFactor: WORKING_SMALL_ORBIT_RADIUS,
|
|
136
|
+
speedFactor: WORKING_SMALL_SPEEDS[orbit % WORKING_SMALL_SPEEDS.length]
|
|
137
|
+
});
|
|
86
138
|
export const working = (t, ctx) => {
|
|
87
139
|
const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
|
|
88
140
|
const origin = originOf(ctx);
|
|
89
141
|
const worldRadius = worldRadiusOf(ctx);
|
|
90
|
-
const
|
|
91
|
-
|
|
142
|
+
const small = WORKING_SMALL_LAYOUT[ctx.size];
|
|
143
|
+
// Small sizes run two opposite trains per orbit (scaled by density); 64px keeps its own swarm.
|
|
144
|
+
const lapsPerOrbit = Math.max(1, Math.round(2 * ctx.density));
|
|
145
|
+
const trainCount = small
|
|
146
|
+
? small.orbits * lapsPerOrbit
|
|
147
|
+
: Math.max(WORKING_MIN_TRAINS_64, sizing.count(WORKING_TRAIN_BASE_COUNT));
|
|
148
|
+
const trainLength = small ? small.trainLength : WORKING_TRAIN_LENGTH;
|
|
149
|
+
const arcStep = (small ? small.arcSpan : WORKING_TRAIN_ARC_SPAN) / (trainLength - 1);
|
|
92
150
|
const dots = [];
|
|
93
151
|
for (let train = 0; train < trainCount; train += 1) {
|
|
94
|
-
const orbit =
|
|
95
|
-
|
|
152
|
+
const orbit = small
|
|
153
|
+
? workingSmallOrbit(train % small.orbits, small.orbits)
|
|
154
|
+
: workingOrbit(train % WORKING_ORBIT_COUNT);
|
|
155
|
+
const leadPhase = small
|
|
156
|
+
? (Math.floor(train / small.orbits) / lapsPerOrbit) * TWO_PI + (train % small.orbits) * 0.9
|
|
157
|
+
: hashUnit(train * 5 + 1) * TWO_PI;
|
|
96
158
|
const leadAngle = leadPhase + t * WORKING_ANGULAR_SPEED * orbit.speedFactor;
|
|
97
159
|
const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
|
|
98
|
-
for (let slot = 0; slot <
|
|
160
|
+
for (let slot = 0; slot < trainLength; slot += 1) {
|
|
99
161
|
const angle = leadAngle - slot * arcStep;
|
|
100
162
|
const planar = {
|
|
101
163
|
x: Math.cos(angle) * orbitRadius,
|
|
@@ -106,17 +168,57 @@ export const working = (t, ctx) => {
|
|
|
106
168
|
const point = rotateZ(tilted, orbit.roll);
|
|
107
169
|
const projected = project(point, origin, worldRadius);
|
|
108
170
|
const seed = train * 13 + slot * 7 + 2;
|
|
109
|
-
const trail = 1 - slot / (
|
|
171
|
+
const trail = 1 - slot / (trainLength - 1);
|
|
110
172
|
const ink = shadeByDepth(projected.depth) +
|
|
111
173
|
(hashUnit(seed) - 0.5) * 0.08 -
|
|
112
174
|
(orbit.weight - 0.5) * WORKING_ORBIT_INK_SPREAD;
|
|
113
175
|
const alpha = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * trail) *
|
|
114
176
|
(WORKING_ORBIT_ALPHA_FLOOR + (1 - WORKING_ORBIT_ALPHA_FLOOR) * orbit.weight);
|
|
115
|
-
const
|
|
177
|
+
const radiusBase = small ? WORKING_SMALL_BASE_RADIUS_PX : WORKING_BASE_RADIUS_PX;
|
|
178
|
+
const radius = sizing.radius(radiusBase) * (0.9 + 0.25 * hashUnit(seed + 3));
|
|
116
179
|
dots.push(toDot(projected, radius, ink, alpha));
|
|
117
180
|
}
|
|
118
181
|
}
|
|
119
|
-
|
|
182
|
+
// 64px only: 32/20px already anchor their orbits with the faint stroke path below, drawn for
|
|
183
|
+
// every size in WORKING_SMALL_LAYOUT.
|
|
184
|
+
if (!small) {
|
|
185
|
+
const ghostDotsPerOrbit = Math.max(8, sizing.countAlongPath(WORKING_GHOST_DOTS_PER_ORBIT));
|
|
186
|
+
for (let orbitIndex = 0; orbitIndex < WORKING_ORBIT_COUNT; orbitIndex += 1) {
|
|
187
|
+
const orbit = workingOrbit(orbitIndex);
|
|
188
|
+
const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
|
|
189
|
+
for (let k = 0; k < ghostDotsPerOrbit; k += 1) {
|
|
190
|
+
const angle = (k / ghostDotsPerOrbit) * TWO_PI;
|
|
191
|
+
const planar = {
|
|
192
|
+
x: Math.cos(angle) * orbitRadius,
|
|
193
|
+
y: 0,
|
|
194
|
+
z: Math.sin(angle) * orbitRadius
|
|
195
|
+
};
|
|
196
|
+
const tilted = rotateX(planar, orbit.tilt);
|
|
197
|
+
const point = rotateZ(tilted, orbit.roll);
|
|
198
|
+
const projected = project(point, origin, worldRadius);
|
|
199
|
+
dots.push(toDot(projected, sizing.radius(WORKING_GHOST_RADIUS_PX), WORKING_GHOST_INK, WORKING_GHOST_ALPHA * (0.4 + 0.6 * projected.depth)));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const paths = small
|
|
204
|
+
? Array.from({ length: small.orbits }, (_unused, orbitIndex) => {
|
|
205
|
+
const orbit = workingSmallOrbit(orbitIndex, small.orbits);
|
|
206
|
+
const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
|
|
207
|
+
const points = Array.from({ length: WORKING_PATH_SEGMENTS + 1 }, (_unusedPoint, j) => {
|
|
208
|
+
const angle = (j / WORKING_PATH_SEGMENTS) * TWO_PI;
|
|
209
|
+
const planar = {
|
|
210
|
+
x: Math.cos(angle) * orbitRadius,
|
|
211
|
+
y: 0,
|
|
212
|
+
z: Math.sin(angle) * orbitRadius
|
|
213
|
+
};
|
|
214
|
+
return project(rotateZ(rotateX(planar, orbit.tilt), orbit.roll), origin, worldRadius);
|
|
215
|
+
});
|
|
216
|
+
return points
|
|
217
|
+
.slice(1)
|
|
218
|
+
.map((to, j) => toStroke(points[j], to, sizing.radius(WORKING_PATH_WIDTH_PX), WORKING_PATH_INK, WORKING_PATH_ALPHA));
|
|
219
|
+
}).flat()
|
|
220
|
+
: [];
|
|
221
|
+
return { dots, strokes: paths };
|
|
120
222
|
};
|
|
121
223
|
// ---------------------------------------------------------------------------
|
|
122
224
|
// searching -- a dotted globe with a meridian band sweeping around it
|
|
@@ -183,10 +285,16 @@ export const searching = (t, ctx) => {
|
|
|
183
285
|
};
|
|
184
286
|
// ---------------------------------------------------------------------------
|
|
185
287
|
// solving -- tiers that snap a quarter turn out of alignment, hold scrambled,
|
|
186
|
-
// then resolve back, on a loop.
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
288
|
+
// then resolve back, on a loop. Each tier's own texture dots rotate with it
|
|
289
|
+
// (reference parity, BZ-6466 follow-up: matching upstream thinking-orbs'
|
|
290
|
+
// `rubik`, which reads solved-vs-scrambled off this same per-band rotation
|
|
291
|
+
// alone), so the misalignment shows as a visible discontinuity in the dot
|
|
292
|
+
// pattern itself; oversized landmark dots add a brighter set of accent
|
|
293
|
+
// points per tier. An earlier tuning also drew fixed seam rings and
|
|
294
|
+
// per-landmark meridian strokes to make the misalignment legible, but next
|
|
295
|
+
// to the reference that wireframe read as a busy skeleton the upstream
|
|
296
|
+
// version doesn't have, so it's gone -- the texture's own rotation already
|
|
297
|
+
// carries the same information.
|
|
190
298
|
// ---------------------------------------------------------------------------
|
|
191
299
|
const SOLVING_BASE_COUNT = 130;
|
|
192
300
|
const SOLVING_BASE_RADIUS_PX = 1.5;
|
|
@@ -200,16 +308,13 @@ const SOLVING_TILT = -0.5;
|
|
|
200
308
|
* cluster relative to its neighbours instead of only reshuffling texture
|
|
201
309
|
* that looks the same before and after. */
|
|
202
310
|
const SOLVING_LANDMARKS_PER_TIER = 3;
|
|
311
|
+
/** One phase for every tier, so in the solved pose each tier's landmarks line up into the same
|
|
312
|
+
* three columns and a turned tier visibly breaks out of them. A per-tier phase scattered them
|
|
313
|
+
* even when solved, leaving nothing for a turn to disturb. */
|
|
314
|
+
const SOLVING_LANDMARK_PHASE = hashUnit(5) * TWO_PI;
|
|
203
315
|
const SOLVING_LANDMARK_RADIUS_PX = 3.1;
|
|
204
316
|
const SOLVING_LANDMARK_INK_BOOST = 0.45;
|
|
205
317
|
const SOLVING_LANDMARK_ALPHA = 0.9;
|
|
206
|
-
/** A static ring of short strokes at each cut between tiers, unaffected by
|
|
207
|
-
* any turn, so the "cut into bands" structure reads even while the puzzle
|
|
208
|
-
* sits solved and nothing is moving. */
|
|
209
|
-
const SOLVING_SEAM_SEGMENTS = 12;
|
|
210
|
-
const SOLVING_SEAM_STROKE_RADIUS = 1.05;
|
|
211
|
-
const SOLVING_SEAM_INK = 0.15;
|
|
212
|
-
const SOLVING_SEAM_ALPHA = 0.5;
|
|
213
318
|
const solvingTurnStart = (tier) => tier * SOLVING_TURN_GAP;
|
|
214
319
|
const solvingTurnEnd = (tier) => solvingTurnStart(tier) + SOLVING_TURN_DURATION;
|
|
215
320
|
const SOLVING_HOLD_START = solvingTurnEnd(SOLVING_TIER_COUNT - 1) + SOLVING_HOLD_GAP;
|
|
@@ -243,8 +348,6 @@ const solvingTierOf = (yUnit) => clamp(Math.floor(((1 - yUnit) / 2) * SOLVING_TI
|
|
|
243
348
|
const solvingTierDirection = (tier) => (hashUnit(tier * 53 + 21) < 0.5 ? -1 : 1);
|
|
244
349
|
/** The y-unit (-1..1) at the centre of a tier's latitude band. */
|
|
245
350
|
const solvingTierCenterYUnit = (tier) => 1 - (2 * tier + 1) / SOLVING_TIER_COUNT;
|
|
246
|
-
/** The y-unit (-1..1) of the fixed cut between one tier and the next. */
|
|
247
|
-
const solvingSeamYUnit = (seam) => 1 - (2 * (seam + 1)) / SOLVING_TIER_COUNT;
|
|
248
351
|
const solvingTurnAngle = (tier, cyclePos) => solvingTierTurn(tier, cyclePos) * solvingTierDirection(tier) * (Math.PI / 2);
|
|
249
352
|
export const solving = (t, ctx) => {
|
|
250
353
|
const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
|
|
@@ -270,8 +373,7 @@ export const solving = (t, ctx) => {
|
|
|
270
373
|
const yUnit = solvingTierCenterYUnit(tier);
|
|
271
374
|
const y = yUnit * worldRadius;
|
|
272
375
|
const ringRadius = Math.sqrt(Math.max(0, 1 - yUnit * yUnit)) * worldRadius;
|
|
273
|
-
const
|
|
274
|
-
const longitude = phase + (slot / SOLVING_LANDMARKS_PER_TIER) * TWO_PI;
|
|
376
|
+
const longitude = SOLVING_LANDMARK_PHASE + (slot / SOLVING_LANDMARKS_PER_TIER) * TWO_PI;
|
|
275
377
|
const base = {
|
|
276
378
|
x: Math.cos(longitude) * ringRadius,
|
|
277
379
|
y,
|
|
@@ -283,23 +385,7 @@ export const solving = (t, ctx) => {
|
|
|
283
385
|
const ink = shadeByDepth(projected.depth) - SOLVING_LANDMARK_INK_BOOST;
|
|
284
386
|
return toDot(projected, sizing.radius(SOLVING_LANDMARK_RADIUS_PX), ink, SOLVING_LANDMARK_ALPHA);
|
|
285
387
|
});
|
|
286
|
-
|
|
287
|
-
const seams = Array.from({ length: SOLVING_TIER_COUNT - 1 }, (_unused, seam) => {
|
|
288
|
-
const yUnit = solvingSeamYUnit(seam);
|
|
289
|
-
const y = yUnit * worldRadius;
|
|
290
|
-
const ringRadius = Math.sqrt(Math.max(0, 1 - yUnit * yUnit)) * worldRadius;
|
|
291
|
-
const ringPoints = Array.from({ length: SOLVING_SEAM_SEGMENTS + 1 }, (_unusedPoint, j) => {
|
|
292
|
-
const angle = (j / SOLVING_SEAM_SEGMENTS) * TWO_PI;
|
|
293
|
-
const point = {
|
|
294
|
-
x: Math.cos(angle) * ringRadius,
|
|
295
|
-
y,
|
|
296
|
-
z: Math.sin(angle) * ringRadius
|
|
297
|
-
};
|
|
298
|
-
return project(rotateX(point, SOLVING_TILT), origin, worldRadius);
|
|
299
|
-
});
|
|
300
|
-
return Array.from({ length: SOLVING_SEAM_SEGMENTS }, (_unusedSegment, j) => toStroke(ringPoints[j], ringPoints[j + 1], seamWidth, SOLVING_SEAM_INK, SOLVING_SEAM_ALPHA));
|
|
301
|
-
}).flat();
|
|
302
|
-
return { dots: [...texture, ...landmarks], strokes: seams };
|
|
388
|
+
return { dots: [...texture, ...landmarks], strokes: [] };
|
|
303
389
|
};
|
|
304
390
|
// ---------------------------------------------------------------------------
|
|
305
391
|
// listening -- stacked horizontal rings with a wave rolling through them
|
|
@@ -307,13 +393,17 @@ export const solving = (t, ctx) => {
|
|
|
307
393
|
/** Fewer, fatter rings at the smaller presets read as distinct stacked bands;
|
|
308
394
|
* six thin rings at 20px used to compress into a single smudge. */
|
|
309
395
|
const LISTENING_RING_COUNT = { 64: 5, 32: 4, 20: 3 };
|
|
396
|
+
/** Dots per ring at 64px, through `countAlongPath` so 32px keeps a denser ring than 20px
|
|
397
|
+
* instead of both falling to the minimum below. */
|
|
310
398
|
const LISTENING_DOTS_PER_RING_BASE = 26;
|
|
311
399
|
/** A ring below this many dots reads as a loose cluster of points rather than
|
|
312
400
|
* a line, especially once it is a small ellipse at 20px. */
|
|
313
401
|
const LISTENING_MIN_DOTS_PER_RING = 10;
|
|
314
402
|
const LISTENING_BASE_RADIUS_PX = 1.5;
|
|
315
403
|
const LISTENING_WAVE_SPEED = 0.6;
|
|
316
|
-
|
|
404
|
+
/** How much a ring's dots grow as the wave crests through it. At 0.55 the crest ate most of the
|
|
405
|
+
* gap between rings at 32/20px, where the rings sit only a few px apart. */
|
|
406
|
+
const LISTENING_SWELL = 0.3;
|
|
317
407
|
const LISTENING_LIFT_INK = 0.45;
|
|
318
408
|
const LISTENING_SPIN_SPEED = 0.12;
|
|
319
409
|
/** A ring's own vertical thickness once tilted is (its radius) x sin(tilt); a
|
|
@@ -322,10 +412,10 @@ const LISTENING_SPIN_SPEED = 0.12;
|
|
|
322
412
|
* neighbouring rings' ellipses swallow the empty space between them. Kept
|
|
323
413
|
* shallow rather than flattened to zero so a ring still reads as an ellipse
|
|
324
414
|
* seen slightly from above, not a flat line. */
|
|
325
|
-
const LISTENING_TILT = -0.
|
|
415
|
+
const LISTENING_TILT = -0.1;
|
|
326
416
|
/** Rings are stacked between +/- this fraction of the world radius, short of
|
|
327
417
|
* the poles so the top and bottom rings stay full circles, not points. */
|
|
328
|
-
const LISTENING_Y_SPAN = 0.
|
|
418
|
+
const LISTENING_Y_SPAN = 0.86;
|
|
329
419
|
const listeningRing = (ring, ringCount, worldRadius) => {
|
|
330
420
|
const yUnit = (-1 + (2 * ring + 1) / ringCount) * LISTENING_Y_SPAN;
|
|
331
421
|
const y = yUnit * worldRadius;
|
|
@@ -339,8 +429,7 @@ export const listening = (t, ctx) => {
|
|
|
339
429
|
const origin = originOf(ctx);
|
|
340
430
|
const worldRadius = worldRadiusOf(ctx);
|
|
341
431
|
const ringCount = LISTENING_RING_COUNT[ctx.size];
|
|
342
|
-
const
|
|
343
|
-
const dotsPerRing = Math.max(LISTENING_MIN_DOTS_PER_RING, Math.round(sizing.count(totalBase) / ringCount));
|
|
432
|
+
const dotsPerRing = Math.max(LISTENING_MIN_DOTS_PER_RING, sizing.countAlongPath(LISTENING_DOTS_PER_RING_BASE));
|
|
344
433
|
const dots = Array.from({ length: ringCount }, (_unused, r) => {
|
|
345
434
|
const ring = listeningRing(r, ringCount, worldRadius);
|
|
346
435
|
const wave = ring.wave(t);
|
|
@@ -18,9 +18,13 @@ export type Projected = {
|
|
|
18
18
|
depth: number;
|
|
19
19
|
scale: number;
|
|
20
20
|
};
|
|
21
|
-
/** The
|
|
21
|
+
/** The scalers `modeSizing` hands back: multiply a mode's own base counts and radii by them. */
|
|
22
22
|
export type ModeSizing = {
|
|
23
|
+
/** For marks spread over an area (a swarm, a globe's surface): scales with `size` squared. */
|
|
23
24
|
count: (base: number) => number;
|
|
25
|
+
/** For marks spaced along a line (a ring, a thread): scales with `size`, so the gap between
|
|
26
|
+
* neighbours keeps the same share of the canvas at every size instead of closing up. */
|
|
27
|
+
countAlongPath: (base: number) => number;
|
|
24
28
|
radius: (base: number) => number;
|
|
25
29
|
};
|
|
26
30
|
/** Rotates a point about the X axis (mixes y and z). */
|
|
@@ -57,7 +61,8 @@ export declare const hashUnit: (seed: number) => number;
|
|
|
57
61
|
/**
|
|
58
62
|
* Turns `(size, density, dotScale)` into a count scaler and a radius scaler,
|
|
59
63
|
* so every mode derives its dot counts and radii the same way instead of
|
|
60
|
-
* keeping its own per-size table. Counts scale with area (`size` squared)
|
|
64
|
+
* keeping its own per-size table. Counts scale with area (`size` squared) for
|
|
65
|
+
* marks spread over a surface and with `size` for marks spaced along a path;
|
|
61
66
|
* radii grow sub-linearly, with a floor that keeps dots visible at 20px.
|
|
62
67
|
*/
|
|
63
68
|
export declare const modeSizing: (size: ModeSize, density: number, dotScale: number) => ModeSizing;
|
|
@@ -100,7 +100,8 @@ const MIN_DOT_RADIUS_PX = 0.6;
|
|
|
100
100
|
/**
|
|
101
101
|
* Turns `(size, density, dotScale)` into a count scaler and a radius scaler,
|
|
102
102
|
* so every mode derives its dot counts and radii the same way instead of
|
|
103
|
-
* keeping its own per-size table. Counts scale with area (`size` squared)
|
|
103
|
+
* keeping its own per-size table. Counts scale with area (`size` squared) for
|
|
104
|
+
* marks spread over a surface and with `size` for marks spaced along a path;
|
|
104
105
|
* radii grow sub-linearly, with a floor that keeps dots visible at 20px.
|
|
105
106
|
*/
|
|
106
107
|
export const modeSizing = (size, density, dotScale) => {
|
|
@@ -110,6 +111,7 @@ export const modeSizing = (size, density, dotScale) => {
|
|
|
110
111
|
const safeDotScale = dotScale > 0 ? dotScale : 0.1;
|
|
111
112
|
return {
|
|
112
113
|
count: (base) => Math.max(1, Math.round(base * areaRatio * safeDensity)),
|
|
114
|
+
countAlongPath: (base) => Math.max(1, Math.round(base * sizeRatio * safeDensity)),
|
|
113
115
|
radius: (base) => Math.max(MIN_DOT_RADIUS_PX, base * sizeRatio ** RADIUS_GROWTH_EXPONENT * safeDotScale)
|
|
114
116
|
};
|
|
115
117
|
};
|
package/dist-wc/index.js
CHANGED
|
@@ -24017,6 +24017,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24017
24017
|
let i = e / REFERENCE_SIZE, a = i * i, o = t > 0 ? t : .1, s = n > 0 ? n : .1;
|
|
24018
24018
|
return {
|
|
24019
24019
|
count: (e) => Math.max(1, Math.round(e * a * o)),
|
|
24020
|
+
countAlongPath: (e) => Math.max(1, Math.round(e * i * o)),
|
|
24020
24021
|
radius: (e) => Math.max(MIN_DOT_RADIUS_PX, e * i ** RADIUS_GROWTH_EXPONENT * s)
|
|
24021
24022
|
};
|
|
24022
24023
|
}, GRAVITY_DEFAULTS = {
|
|
@@ -24078,11 +24079,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24078
24079
|
width: n,
|
|
24079
24080
|
ink: clamp01$2(i),
|
|
24080
24081
|
alpha: clamp01$2(a)
|
|
24081
|
-
}), WORKING_TRAIN_BASE_COUNT =
|
|
24082
|
-
64: 3,
|
|
24083
|
-
32: 3,
|
|
24084
|
-
20: 12
|
|
24085
|
-
}, WORKING_TRAIN_LENGTH = 5, WORKING_TRAIN_ARC_SPAN = .5, WORKING_BASE_RADIUS_PX = 1.6, WORKING_ORBIT_COUNT = 6, WORKING_ANGULAR_SPEED = 1.1, WORKING_ORBIT_INK_SPREAD = .3, WORKING_ORBIT_ALPHA_FLOOR = .65, WORKING_TRAIL_ALPHA_FLOOR = .35, workingOrbit = (e) => {
|
|
24082
|
+
}), WORKING_TRAIN_BASE_COUNT = 12, WORKING_MIN_TRAINS_64 = 3, WORKING_TRAIN_LENGTH = 3, WORKING_TRAIN_ARC_SPAN = .5, WORKING_BASE_RADIUS_PX = 1.3, WORKING_SMALL_BASE_RADIUS_PX = 2.2, WORKING_ORBIT_COUNT = 6, WORKING_ANGULAR_SPEED = 1.1, WORKING_ORBIT_INK_SPREAD = .3, WORKING_ORBIT_ALPHA_FLOOR = .65, WORKING_TRAIL_ALPHA_FLOOR = .35, WORKING_GHOST_DOTS_PER_ORBIT = 40, WORKING_GHOST_RADIUS_PX = .7, WORKING_GHOST_INK = .4, WORKING_GHOST_ALPHA = .5, workingOrbit = (e) => {
|
|
24086
24083
|
let t = e * 31 + 7;
|
|
24087
24084
|
return {
|
|
24088
24085
|
tilt: (hashUnit(t) - .5) * Math.PI,
|
|
@@ -24091,22 +24088,68 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24091
24088
|
speedFactor: .6 + 1.3 * hashUnit(t + 9),
|
|
24092
24089
|
weight: .35 + .65 * hashUnit(t + 15)
|
|
24093
24090
|
};
|
|
24094
|
-
},
|
|
24095
|
-
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
24100
|
-
|
|
24091
|
+
}, WORKING_SMALL_LAYOUT = {
|
|
24092
|
+
32: {
|
|
24093
|
+
orbits: 4,
|
|
24094
|
+
trainLength: 4,
|
|
24095
|
+
arcSpan: .75
|
|
24096
|
+
},
|
|
24097
|
+
20: {
|
|
24098
|
+
orbits: 3,
|
|
24099
|
+
trainLength: 4,
|
|
24100
|
+
arcSpan: .9
|
|
24101
|
+
}
|
|
24102
|
+
}, WORKING_SMALL_ORBIT_TILT = .5, WORKING_PATH_SEGMENTS = 24, WORKING_PATH_WIDTH_PX = .8, WORKING_PATH_INK = .5, WORKING_PATH_ALPHA = .45, WORKING_SMALL_ORBIT_RADIUS = .85, WORKING_SMALL_SPEEDS = [
|
|
24103
|
+
.8,
|
|
24104
|
+
1.15,
|
|
24105
|
+
1.45,
|
|
24106
|
+
1
|
|
24107
|
+
], workingSmallOrbit = (e, t) => ({
|
|
24108
|
+
...workingOrbit(e),
|
|
24109
|
+
tilt: WORKING_SMALL_ORBIT_TILT,
|
|
24110
|
+
roll: e / t * Math.PI,
|
|
24111
|
+
orbitRadiusFactor: WORKING_SMALL_ORBIT_RADIUS,
|
|
24112
|
+
speedFactor: WORKING_SMALL_SPEEDS[e % WORKING_SMALL_SPEEDS.length]
|
|
24113
|
+
}), working = (e, t) => {
|
|
24114
|
+
let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = WORKING_SMALL_LAYOUT[t.size], s = Math.max(1, Math.round(2 * t.density)), c = o ? o.orbits * s : Math.max(WORKING_MIN_TRAINS_64, n.count(WORKING_TRAIN_BASE_COUNT)), l = o ? o.trainLength : WORKING_TRAIN_LENGTH, u = (o ? o.arcSpan : WORKING_TRAIN_ARC_SPAN) / (l - 1), f = [];
|
|
24115
|
+
for (let t = 0; t < c; t += 1) {
|
|
24116
|
+
let c = o ? workingSmallOrbit(t % o.orbits, o.orbits) : workingOrbit(t % WORKING_ORBIT_COUNT), p = (o ? Math.floor(t / o.orbits) / s * TWO_PI$1 + t % o.orbits * .9 : hashUnit(t * 5 + 1) * TWO_PI$1) + e * WORKING_ANGULAR_SPEED * c.speedFactor, h = a * c.orbitRadiusFactor;
|
|
24117
|
+
for (let e = 0; e < l; e += 1) {
|
|
24118
|
+
let s = p - e * u, S = project(rotateZ(rotateX({
|
|
24119
|
+
x: Math.cos(s) * h,
|
|
24101
24120
|
y: 0,
|
|
24102
|
-
z: Math.sin(
|
|
24103
|
-
},
|
|
24104
|
-
|
|
24121
|
+
z: Math.sin(s) * h
|
|
24122
|
+
}, c.tilt), c.roll), i, a), C = t * 13 + e * 7 + 2, k = 1 - e / (l - 1), R = shadeByDepth(S.depth) + (hashUnit(C) - .5) * .08 - (c.weight - .5) * WORKING_ORBIT_INK_SPREAD, te = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * k) * (WORKING_ORBIT_ALPHA_FLOOR + (1 - WORKING_ORBIT_ALPHA_FLOOR) * c.weight), ne = o ? WORKING_SMALL_BASE_RADIUS_PX : WORKING_BASE_RADIUS_PX, re = n.radius(ne) * (.9 + .25 * hashUnit(C + 3));
|
|
24123
|
+
f.push(toDot(S, re, R, te));
|
|
24124
|
+
}
|
|
24125
|
+
}
|
|
24126
|
+
if (!o) {
|
|
24127
|
+
let e = Math.max(8, n.countAlongPath(WORKING_GHOST_DOTS_PER_ORBIT));
|
|
24128
|
+
for (let t = 0; t < WORKING_ORBIT_COUNT; t += 1) {
|
|
24129
|
+
let o = workingOrbit(t), s = a * o.orbitRadiusFactor;
|
|
24130
|
+
for (let t = 0; t < e; t += 1) {
|
|
24131
|
+
let c = t / e * TWO_PI$1, l = project(rotateZ(rotateX({
|
|
24132
|
+
x: Math.cos(c) * s,
|
|
24133
|
+
y: 0,
|
|
24134
|
+
z: Math.sin(c) * s
|
|
24135
|
+
}, o.tilt), o.roll), i, a);
|
|
24136
|
+
f.push(toDot(l, n.radius(WORKING_GHOST_RADIUS_PX), WORKING_GHOST_INK, WORKING_GHOST_ALPHA * (.4 + .6 * l.depth)));
|
|
24137
|
+
}
|
|
24105
24138
|
}
|
|
24106
24139
|
}
|
|
24107
24140
|
return {
|
|
24108
|
-
dots:
|
|
24109
|
-
strokes:
|
|
24141
|
+
dots: f,
|
|
24142
|
+
strokes: o ? Array.from({ length: o.orbits }, (e, t) => {
|
|
24143
|
+
let s = workingSmallOrbit(t, o.orbits), c = a * s.orbitRadiusFactor, l = Array.from({ length: 25 }, (e, t) => {
|
|
24144
|
+
let n = t / WORKING_PATH_SEGMENTS * TWO_PI$1;
|
|
24145
|
+
return project(rotateZ(rotateX({
|
|
24146
|
+
x: Math.cos(n) * c,
|
|
24147
|
+
y: 0,
|
|
24148
|
+
z: Math.sin(n) * c
|
|
24149
|
+
}, s.tilt), s.roll), i, a);
|
|
24150
|
+
});
|
|
24151
|
+
return l.slice(1).map((e, t) => toStroke(l[t], e, n.radius(WORKING_PATH_WIDTH_PX), WORKING_PATH_INK, WORKING_PATH_ALPHA));
|
|
24152
|
+
}).flat() : []
|
|
24110
24153
|
};
|
|
24111
24154
|
}, SEARCHING_BASE_COUNT = 150, SEARCHING_BASE_RADIUS_PX = 1.5, SEARCHING_BAND_HALF_WIDTH = .42, SEARCHING_SWEEP_SPEED = 1.15, SEARCHING_SPIN_SPEED = .18, SEARCHING_TILT = -.55, SEARCHING_ARC_SEGMENTS = 10, SEARCHING_ARC_LATITUDE_SPAN = .92, SEARCHING_BAND_INK_BOOST = .75, SEARCHING_BAND_ALPHA_BOOST = .35, SEARCHING_BAND_RADIUS_BOOST = 1.3, SEARCHING_ARC_STROKE_RADIUS = 2.2, SEARCHING_ARC_INK = 0, SEARCHING_ARC_ALPHA = .7, angleDistance = (e, t) => {
|
|
24112
24155
|
let n = Math.abs(e - t) % TWO_PI$1;
|
|
@@ -24127,40 +24170,30 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24127
24170
|
dots: c,
|
|
24128
24171
|
strokes: Array.from({ length: SEARCHING_ARC_SEGMENTS }, (e, t) => toStroke(l[t], l[t + 1], u, SEARCHING_ARC_INK, SEARCHING_ARC_ALPHA))
|
|
24129
24172
|
};
|
|
24130
|
-
}, SOLVING_BASE_COUNT = 130, SOLVING_BASE_RADIUS_PX = 1.5, SOLVING_TIER_COUNT = 4, SOLVING_TURN_DURATION = .16, SOLVING_TURN_GAP = .3, SOLVING_HOLD_GAP = .55, SOLVING_TILT = -.5, SOLVING_LANDMARKS_PER_TIER = 3,
|
|
24173
|
+
}, SOLVING_BASE_COUNT = 130, SOLVING_BASE_RADIUS_PX = 1.5, SOLVING_TIER_COUNT = 4, SOLVING_TURN_DURATION = .16, SOLVING_TURN_GAP = .3, SOLVING_HOLD_GAP = .55, SOLVING_TILT = -.5, SOLVING_LANDMARKS_PER_TIER = 3, SOLVING_LANDMARK_PHASE = hashUnit(5) * TWO_PI$1, SOLVING_LANDMARK_RADIUS_PX = 3.1, SOLVING_LANDMARK_INK_BOOST = .45, SOLVING_LANDMARK_ALPHA = .9, solvingTurnStart = (e) => e * SOLVING_TURN_GAP, solvingTurnEnd = (e) => solvingTurnStart(e) + SOLVING_TURN_DURATION, SOLVING_HOLD_START = solvingTurnEnd(SOLVING_TIER_COUNT - 1) + SOLVING_HOLD_GAP, solvingReturnStart = (e) => SOLVING_HOLD_START + (SOLVING_TIER_COUNT - 1 - e) * SOLVING_TURN_GAP, solvingReturnEnd = (e) => solvingReturnStart(e) + SOLVING_TURN_DURATION, SOLVING_CYCLE = solvingReturnEnd(0) + SOLVING_HOLD_GAP, solvingTierTurn = (e, t) => {
|
|
24131
24174
|
let n = solvingTurnStart(e), i = solvingTurnEnd(e), a = solvingReturnStart(e), o = solvingReturnEnd(e);
|
|
24132
24175
|
return t <= n ? 0 : t < i ? easeOutCubic((t - n) / SOLVING_TURN_DURATION) : t < a ? 1 : t < o ? 1 - easeInOutCubic((t - a) / SOLVING_TURN_DURATION) : 0;
|
|
24133
|
-
}, solvingTierOf = (e) => clamp$1(Math.floor((1 - e) / 2 * SOLVING_TIER_COUNT), 0, SOLVING_TIER_COUNT - 1), solvingTierDirection = (e) => hashUnit(e * 53 + 21) < .5 ? -1 : 1, solvingTierCenterYUnit = (e) => 1 - (2 * e + 1) / SOLVING_TIER_COUNT,
|
|
24176
|
+
}, solvingTierOf = (e) => clamp$1(Math.floor((1 - e) / 2 * SOLVING_TIER_COUNT), 0, SOLVING_TIER_COUNT - 1), solvingTierDirection = (e) => hashUnit(e * 53 + 21) < .5 ? -1 : 1, solvingTierCenterYUnit = (e) => 1 - (2 * e + 1) / SOLVING_TIER_COUNT, solvingTurnAngle = (e, t) => solvingTierTurn(e, t) * solvingTierDirection(e) * (Math.PI / 2), solving = (e, t) => {
|
|
24134
24177
|
let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = n.count(SOLVING_BASE_COUNT), s = e - SOLVING_CYCLE * Math.floor(e / SOLVING_CYCLE), c = Array.from({ length: o }, (e, t) => {
|
|
24135
24178
|
let c = sampleSphere(t, o, a), l = project(rotateX(rotateY(c, solvingTurnAngle(solvingTierOf(a > 0 ? c.y / a : 0), s)), SOLVING_TILT), i, a), u = shadeByDepth(l.depth), f = .55 + .3 * hashUnit(t * 3 + 9);
|
|
24136
24179
|
return toDot(l, n.radius(SOLVING_BASE_RADIUS_PX), u, f);
|
|
24137
24180
|
}), l = Array.from({ length: SOLVING_TIER_COUNT * SOLVING_LANDMARKS_PER_TIER }, (e, t) => {
|
|
24138
|
-
let o = Math.floor(t / SOLVING_LANDMARKS_PER_TIER), c = t % SOLVING_LANDMARKS_PER_TIER, l = solvingTierCenterYUnit(o), u = l * a, f = Math.sqrt(Math.max(0, 1 - l * l)) * a, p =
|
|
24181
|
+
let o = Math.floor(t / SOLVING_LANDMARKS_PER_TIER), c = t % SOLVING_LANDMARKS_PER_TIER, l = solvingTierCenterYUnit(o), u = l * a, f = Math.sqrt(Math.max(0, 1 - l * l)) * a, p = SOLVING_LANDMARK_PHASE + c / SOLVING_LANDMARKS_PER_TIER * TWO_PI$1, h = project(rotateX(rotateY({
|
|
24139
24182
|
x: Math.cos(p) * f,
|
|
24140
24183
|
y: u,
|
|
24141
24184
|
z: Math.sin(p) * f
|
|
24142
24185
|
}, solvingTurnAngle(o, s)), SOLVING_TILT), i, a), S = shadeByDepth(h.depth) - SOLVING_LANDMARK_INK_BOOST;
|
|
24143
24186
|
return toDot(h, n.radius(SOLVING_LANDMARK_RADIUS_PX), S, SOLVING_LANDMARK_ALPHA);
|
|
24144
|
-
})
|
|
24145
|
-
let n = solvingSeamYUnit(t), o = n * a, s = Math.sqrt(Math.max(0, 1 - n * n)) * a, c = Array.from({ length: 13 }, (e, t) => {
|
|
24146
|
-
let n = t / SOLVING_SEAM_SEGMENTS * TWO_PI$1;
|
|
24147
|
-
return project(rotateX({
|
|
24148
|
-
x: Math.cos(n) * s,
|
|
24149
|
-
y: o,
|
|
24150
|
-
z: Math.sin(n) * s
|
|
24151
|
-
}, SOLVING_TILT), i, a);
|
|
24152
|
-
});
|
|
24153
|
-
return Array.from({ length: SOLVING_SEAM_SEGMENTS }, (e, t) => toStroke(c[t], c[t + 1], u, SOLVING_SEAM_INK, SOLVING_SEAM_ALPHA));
|
|
24154
|
-
}).flat();
|
|
24187
|
+
});
|
|
24155
24188
|
return {
|
|
24156
24189
|
dots: [...c, ...l],
|
|
24157
|
-
strokes:
|
|
24190
|
+
strokes: []
|
|
24158
24191
|
};
|
|
24159
24192
|
}, LISTENING_RING_COUNT = {
|
|
24160
24193
|
64: 5,
|
|
24161
24194
|
32: 4,
|
|
24162
24195
|
20: 3
|
|
24163
|
-
}, LISTENING_DOTS_PER_RING_BASE = 26, LISTENING_MIN_DOTS_PER_RING = 10, LISTENING_BASE_RADIUS_PX = 1.5, LISTENING_WAVE_SPEED = .6, LISTENING_SWELL = .
|
|
24196
|
+
}, LISTENING_DOTS_PER_RING_BASE = 26, LISTENING_MIN_DOTS_PER_RING = 10, LISTENING_BASE_RADIUS_PX = 1.5, LISTENING_WAVE_SPEED = .6, LISTENING_SWELL = .3, LISTENING_LIFT_INK = .45, LISTENING_SPIN_SPEED = .12, LISTENING_TILT = -.1, LISTENING_Y_SPAN = .86, listeningRing = (e, t, n) => {
|
|
24164
24197
|
let i = (-1 + (2 * e + 1) / t) * LISTENING_Y_SPAN * n;
|
|
24165
24198
|
return {
|
|
24166
24199
|
y: i,
|
|
@@ -24169,15 +24202,15 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24169
24202
|
wave: (n) => (Math.sin(TWO_PI$1 * (e / t - n * LISTENING_WAVE_SPEED)) + 1) / 2
|
|
24170
24203
|
};
|
|
24171
24204
|
}, listening = (e, t) => {
|
|
24172
|
-
let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = LISTENING_RING_COUNT[t.size], s =
|
|
24205
|
+
let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = LISTENING_RING_COUNT[t.size], s = Math.max(LISTENING_MIN_DOTS_PER_RING, n.countAlongPath(LISTENING_DOTS_PER_RING_BASE));
|
|
24173
24206
|
return {
|
|
24174
|
-
dots: Array.from({ length: o }, (t,
|
|
24175
|
-
let l = listeningRing(
|
|
24176
|
-
return Array.from({ length:
|
|
24177
|
-
let
|
|
24178
|
-
x: Math.cos(
|
|
24207
|
+
dots: Array.from({ length: o }, (t, c) => {
|
|
24208
|
+
let l = listeningRing(c, o, a), u = l.wave(e);
|
|
24209
|
+
return Array.from({ length: s }, (t, o) => {
|
|
24210
|
+
let c = o / s * TWO_PI$1 + l.phase, f = project(rotateY(rotateX({
|
|
24211
|
+
x: Math.cos(c) * l.ringRadius,
|
|
24179
24212
|
y: l.y,
|
|
24180
|
-
z: Math.sin(
|
|
24213
|
+
z: Math.sin(c) * l.ringRadius
|
|
24181
24214
|
}, LISTENING_TILT), e * LISTENING_SPIN_SPEED), i, a), p = shadeByDepth(f.depth) - u * LISTENING_LIFT_INK, h = .6 + u * .3;
|
|
24182
24215
|
return toDot(f, n.radius(LISTENING_BASE_RADIUS_PX) * (1 + u * LISTENING_SWELL), p, h);
|
|
24183
24216
|
});
|
|
@@ -24191,7 +24224,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24191
24224
|
}), squaredDistance = (e, t) => {
|
|
24192
24225
|
let n = e.x - t.x, i = e.y - t.y, a = e.z - t.z;
|
|
24193
24226
|
return n * n + i * i + a * a;
|
|
24194
|
-
}, NODE_BASE_COUNT =
|
|
24227
|
+
}, NODE_BASE_COUNT = 10, MIN_NODE_COUNT = 6, NEIGHBOR_COUNT = 2, NODE_BASE_RADIUS = 3.4, PULSE_BASE_RADIUS = 1.4, EDGE_BASE_WIDTH = 1.1, NODE_ROTATE_SPEED = .35, NODE_TILT = .45, EDGE_FLICKER_SPEED = .5, EDGE_MIN_ALPHA = .25, PULSE_SPEED = .6, nearestNeighborEdges = (e, t) => {
|
|
24195
24228
|
let n = e.map((e, t) => t), i = n.flatMap((i) => n.filter((e) => e !== i).sort((t, n) => squaredDistance(e[i], e[t]) - squaredDistance(e[i], e[n])).slice(0, t).map((e) => i < e ? [i, e] : [e, i]));
|
|
24196
24229
|
return i.filter(([e, t], n) => i.findIndex(([n, i]) => n === e && i === t) === n);
|
|
24197
24230
|
}, connecting = (e, t) => {
|
|
@@ -24229,69 +24262,97 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24229
24262
|
dots: [...l, ...p],
|
|
24230
24263
|
strokes: f
|
|
24231
24264
|
};
|
|
24232
|
-
}, STRAND_COUNT = 3, STRAND_POINTS_BASE_COUNT =
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24265
|
+
}, STRAND_COUNT = 3, STRAND_POINTS_BASE_COUNT = 40, MIN_STRAND_POINTS = 16, STRAND_INCLINATION = .6, STRAND_WAVE_COUNT = 3, STRAND_WAVE_AMPLITUDE = .12, STRAND_WAVE_SPEED = .9, STRAND_ROTATE_SPEED = .3, STRAND_TILT = 1.1, STRAND_DOT_BASE_RADIUS = 1.6, STRAND_STROKE_BASE_WIDTH = 1.4, STRAND_STROKE_SIZE_FACTOR = {
|
|
24266
|
+
64: 1,
|
|
24267
|
+
32: 1.2,
|
|
24268
|
+
20: 1.5
|
|
24269
|
+
}, STRAND_DOT_SIZE_FACTOR = {
|
|
24270
|
+
64: 1,
|
|
24271
|
+
32: .85,
|
|
24272
|
+
20: .7
|
|
24273
|
+
}, STRAND_DEPTH_INK = .45, STRAND_SHADE_STEP = .1, STRAND_GHOST_BASE_COUNT = 110, STRAND_GHOST_RADIUS_PX = .5, STRAND_GHOST_INK = .3, STRAND_GHOST_ALPHA_BASE = .16, STRAND_GHOST_ALPHA_DEPTH = .22, weavingGhostCount = (e) => Math.max(20, e.count(STRAND_GHOST_BASE_COUNT)), strandBaseAngle = (e) => e / STRAND_COUNT * Math.PI * 2, strandPoint = (e, t, n, i, a) => {
|
|
24274
|
+
let o = t / (n - 1) * Math.PI * 2, s = STRAND_WAVE_AMPLITUDE * Math.sin(o * STRAND_WAVE_COUNT + a * STRAND_WAVE_SPEED + strandBaseAngle(e));
|
|
24275
|
+
return rotateX(rotateY(rotateX({
|
|
24276
|
+
x: i * Math.cos(s) * Math.cos(o),
|
|
24277
|
+
y: i * Math.sin(s),
|
|
24278
|
+
z: i * Math.cos(s) * Math.sin(o)
|
|
24279
|
+
}, STRAND_INCLINATION), strandBaseAngle(e) + a * STRAND_ROTATE_SPEED), STRAND_TILT);
|
|
24239
24280
|
}, weaving = (e, t) => {
|
|
24240
|
-
let { sizing: n, originPx: i, sphereRadius: a } = sphereGeometry(t), o = Math.max(MIN_STRAND_POINTS, n.
|
|
24281
|
+
let { sizing: n, originPx: i, sphereRadius: a } = sphereGeometry(t), o = Math.max(MIN_STRAND_POINTS, n.countAlongPath(STRAND_POINTS_BASE_COUNT)), s = Array.from({ length: STRAND_COUNT }, (t, n) => Array.from({ length: o }, (t, s) => project(strandPoint(n, s, o, a, e), i, a))), c = s.flatMap((e, i) => e.map((e) => ({
|
|
24282
|
+
x: e.x,
|
|
24283
|
+
y: e.y,
|
|
24284
|
+
depth: e.depth,
|
|
24285
|
+
radius: n.radius(STRAND_DOT_BASE_RADIUS) * STRAND_DOT_SIZE_FACTOR[t.size],
|
|
24286
|
+
ink: clamp01$1((1 - e.depth) * STRAND_DEPTH_INK + i * STRAND_SHADE_STEP),
|
|
24287
|
+
alpha: .55 + e.depth * .4
|
|
24288
|
+
}))), l = s.flatMap((e, i) => e.slice(1).map((a, o) => {
|
|
24289
|
+
let s = e[o], c = (s.depth + a.depth) / 2;
|
|
24290
|
+
return {
|
|
24291
|
+
x1: s.x,
|
|
24292
|
+
y1: s.y,
|
|
24293
|
+
x2: a.x,
|
|
24294
|
+
y2: a.y,
|
|
24295
|
+
depth: c,
|
|
24296
|
+
width: n.radius(STRAND_STROKE_BASE_WIDTH) * STRAND_STROKE_SIZE_FACTOR[t.size],
|
|
24297
|
+
ink: clamp01$1((1 - c) * STRAND_DEPTH_INK + i * STRAND_SHADE_STEP),
|
|
24298
|
+
alpha: .6 + c * .35
|
|
24299
|
+
};
|
|
24300
|
+
})), u = weavingGhostCount(n), f = Array.from({ length: u }, (t, o) => {
|
|
24301
|
+
let s = project(rotateX(rotateY(sampleSphere(o, u, a), e * STRAND_ROTATE_SPEED), STRAND_TILT), i, a);
|
|
24302
|
+
return {
|
|
24303
|
+
x: s.x,
|
|
24304
|
+
y: s.y,
|
|
24305
|
+
depth: s.depth,
|
|
24306
|
+
radius: n.radius(STRAND_GHOST_RADIUS_PX),
|
|
24307
|
+
ink: STRAND_GHOST_INK,
|
|
24308
|
+
alpha: clamp01$1(STRAND_GHOST_ALPHA_BASE + STRAND_GHOST_ALPHA_DEPTH * s.depth)
|
|
24309
|
+
};
|
|
24310
|
+
});
|
|
24241
24311
|
return {
|
|
24242
|
-
dots:
|
|
24243
|
-
|
|
24244
|
-
y: e.y,
|
|
24245
|
-
depth: e.depth,
|
|
24246
|
-
radius: n.radius(STRAND_DOT_BASE_RADIUS),
|
|
24247
|
-
ink: clamp01$1(1 - e.depth * .8 + t * .08),
|
|
24248
|
-
alpha: .55 + e.depth * .4
|
|
24249
|
-
}))),
|
|
24250
|
-
strokes: s.flatMap((e, t) => e.slice(1).map((i, a) => {
|
|
24251
|
-
let o = e[a], s = (o.depth + i.depth) / 2;
|
|
24252
|
-
return {
|
|
24253
|
-
x1: o.x,
|
|
24254
|
-
y1: o.y,
|
|
24255
|
-
x2: i.x,
|
|
24256
|
-
y2: i.y,
|
|
24257
|
-
depth: s,
|
|
24258
|
-
width: n.radius(STRAND_STROKE_BASE_WIDTH),
|
|
24259
|
-
ink: clamp01$1(1 - s * .75 + t * .06),
|
|
24260
|
-
alpha: .6 + s * .35
|
|
24261
|
-
};
|
|
24262
|
-
}))
|
|
24312
|
+
dots: [...c, ...f],
|
|
24313
|
+
strokes: l
|
|
24263
24314
|
};
|
|
24264
|
-
}, TWO_PI = Math.PI * 2, FORM_RADIUS_RATIO = .4, clamp01 = (e) => Math.max(0, Math.min(1, e)), depthToInk = (e) => clamp01(1 - e), depthToAlpha = (e) => clamp01(.5 + e * .5), depthToRadiusFactor = (e) => .7 + e * .3, dotFrom = (e, t, n, i) => ({
|
|
24315
|
+
}, TWO_PI = Math.PI * 2, FORM_RADIUS_RATIO = .4, clamp01 = (e) => Math.max(0, Math.min(1, e)), FAR_INK = .6, depthToInk = (e) => clamp01((1 - e) * FAR_INK), depthToAlpha = (e) => clamp01(.5 + e * .5), depthToRadiusFactor = (e) => .7 + e * .3, dotFrom = (e, t, n, i) => ({
|
|
24265
24316
|
x: e.x,
|
|
24266
24317
|
y: e.y,
|
|
24267
24318
|
depth: e.depth,
|
|
24268
24319
|
radius: n.radius(t) * depthToRadiusFactor(e.depth) * i,
|
|
24269
24320
|
ink: depthToInk(e.depth),
|
|
24270
24321
|
alpha: depthToAlpha(e.depth)
|
|
24271
|
-
}), place = (e, t, n, i, a, o, s, c) => dotFrom(project(rotateX(rotateY(e, t), n), i, a), o, s, c), EMPTY_STROKES = [],
|
|
24272
|
-
64:
|
|
24273
|
-
32:
|
|
24274
|
-
20:
|
|
24275
|
-
}, RIBBON_HALF_WIDTH = .
|
|
24276
|
-
let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = RIBBON_THREADS[t.size], s =
|
|
24322
|
+
}), place = (e, t, n, i, a, o, s, c) => dotFrom(project(rotateX(rotateY(e, t), n), i, a), o, s, c), EMPTY_STROKES = [], RIBBON_PER_THREAD_BASE = 36, RIBBON_BASE_DOT_RADIUS = 1.2, RIBBON_THREADS = {
|
|
24323
|
+
64: 5,
|
|
24324
|
+
32: 4,
|
|
24325
|
+
20: 3
|
|
24326
|
+
}, RIBBON_HALF_WIDTH = .3, RIBBON_AXIS_TILT = .68, RIBBON_RIPPLE_WAVES = 3, RIBBON_RIPPLE_SPEED = 1.15, RIBBON_RIPPLE_AMPLITUDE = .18, RIBBON_THREAD_JITTER = .1, RIBBON_SPIN_SPEED = .48, RIBBON_CAMERA_TILT = .45, RIBBON_EDGE_ALPHA_FLOOR = .5, RIBBON_EDGE_INK_LIFT = .22, RIBBON_GHOST_BASE_COUNT = 110, RIBBON_GHOST_RADIUS_PX = .5, RIBBON_GHOST_INK = .3, RIBBON_GHOST_ALPHA_BASE = .16, RIBBON_GHOST_ALPHA_DEPTH = .22, composing = (e, t) => {
|
|
24327
|
+
let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = RIBBON_THREADS[t.size], s = n.countAlongPath(RIBBON_PER_THREAD_BASE), c = o * s, l = Math.max(20, n.count(RIBBON_GHOST_BASE_COUNT)), u = Array.from({ length: l }, (e, t) => {
|
|
24328
|
+
let o = project(rotateX(sampleSphere(t, l, i), RIBBON_CAMERA_TILT), a, i);
|
|
24329
|
+
return {
|
|
24330
|
+
x: o.x,
|
|
24331
|
+
y: o.y,
|
|
24332
|
+
depth: o.depth,
|
|
24333
|
+
radius: n.radius(RIBBON_GHOST_RADIUS_PX),
|
|
24334
|
+
ink: RIBBON_GHOST_INK,
|
|
24335
|
+
alpha: clamp01(RIBBON_GHOST_ALPHA_BASE + RIBBON_GHOST_ALPHA_DEPTH * o.depth)
|
|
24336
|
+
};
|
|
24337
|
+
});
|
|
24277
24338
|
for (let t = 0; t < c; t += 1) {
|
|
24278
|
-
let c = t % o,
|
|
24339
|
+
let c = t % o, l = Math.floor(t / o), f = o > 1 ? c / (o - 1) * 2 - 1 : 0, p = l / s * TWO_PI, h = Math.sin(p * RIBBON_RIPPLE_WAVES + e * RIBBON_RIPPLE_SPEED) * RIBBON_RIPPLE_AMPLITUDE, S = f * RIBBON_HALF_WIDTH + h, C = rotateZ({
|
|
24279
24340
|
x: Math.cos(S) * Math.cos(p) * i,
|
|
24280
24341
|
y: Math.sin(S) * i,
|
|
24281
24342
|
z: Math.cos(S) * Math.sin(p) * i
|
|
24282
24343
|
}, RIBBON_AXIS_TILT), k = 1 - RIBBON_THREAD_JITTER / 2 + hashUnit(t * 7 + 1) * RIBBON_THREAD_JITTER, R = place(C, e * RIBBON_SPIN_SPEED, RIBBON_CAMERA_TILT, a, i, RIBBON_BASE_DOT_RADIUS, n, k), te = Math.abs(f);
|
|
24283
|
-
|
|
24344
|
+
u.push({
|
|
24284
24345
|
...R,
|
|
24285
24346
|
ink: clamp01(R.ink + te * RIBBON_EDGE_INK_LIFT),
|
|
24286
24347
|
alpha: clamp01(R.alpha * (1 - te * (1 - RIBBON_EDGE_ALPHA_FLOOR)))
|
|
24287
24348
|
});
|
|
24288
24349
|
}
|
|
24289
24350
|
return {
|
|
24290
|
-
dots:
|
|
24351
|
+
dots: u,
|
|
24291
24352
|
strokes: EMPTY_STROKES
|
|
24292
24353
|
};
|
|
24293
|
-
}, BREATHING_BASE_COUNT =
|
|
24294
|
-
let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * BREATHING_WORLD_RADIUS_RATIO, a = t.size / 2, o = n.
|
|
24354
|
+
}, BREATHING_BASE_COUNT = 32, BREATHING_BASE_DOT_RADIUS = 1.7, BREATHING_WORLD_RADIUS_RATIO = .34, BREATHING_AMPLITUDE = .09, BREATHING_SPEED = .55, BREATHING_SHAPE_HARMONIC = 5, BREATHING_SHAPE_SPEED = .22, BREATHING_SHAPE_AMPLITUDE = .035, BREATHING_SPIN_SPEED = .12, BREATHING_TILT = .62, breathing = (e, t) => {
|
|
24355
|
+
let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * BREATHING_WORLD_RADIUS_RATIO, a = t.size / 2, o = n.countAlongPath(BREATHING_BASE_COUNT), s = Math.sin(e * BREATHING_SPEED), c = [];
|
|
24295
24356
|
for (let t = 0; t < o; t += 1) {
|
|
24296
24357
|
let l = t / o * TWO_PI, u = Math.sin(l * BREATHING_SHAPE_HARMONIC + e * BREATHING_SHAPE_SPEED) * BREATHING_SHAPE_AMPLITUDE, f = 1 + s * BREATHING_AMPLITUDE + u, p = {
|
|
24297
24358
|
x: Math.cos(l) * f * i,
|
|
@@ -24304,11 +24365,11 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
|
|
|
24304
24365
|
dots: c,
|
|
24305
24366
|
strokes: EMPTY_STROKES
|
|
24306
24367
|
};
|
|
24307
|
-
}, SHAPING_BASE_COUNT =
|
|
24368
|
+
}, SHAPING_BASE_COUNT = 36, SHAPING_BASE_DOT_RADIUS = 1.7, SHAPING_CYCLE_SECONDS = 12, SHAPING_SPIN_SPEED = 2 * Math.PI / SHAPING_CYCLE_SECONDS, SHAPING_TILT = .6, polygonRadius = (e, t) => {
|
|
24308
24369
|
let n = TWO_PI / t, i = n / 2, a = (e % n + n) % n;
|
|
24309
24370
|
return Math.cos(i) / Math.cos(a - i);
|
|
24310
24371
|
}, lerp = (e, t, n) => e + (t - e) * n, shaping = (e, t) => {
|
|
24311
|
-
let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = n.
|
|
24372
|
+
let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = n.countAlongPath(SHAPING_BASE_COUNT), s = (e / SHAPING_CYCLE_SECONDS % 1 + 1) % 1 * 3, c = Math.min(2, Math.floor(s)), l = easeInOutCubic(s - c), u = [];
|
|
24312
24373
|
for (let t = 0; t < o; t += 1) {
|
|
24313
24374
|
let s = t / o * TWO_PI, f = polygonRadius(s, 3), p = polygonRadius(s, 4), h;
|
|
24314
24375
|
h = c === 0 ? lerp(1, f, l) : c === 1 ? lerp(f, p, l) : lerp(p, 1, l);
|