packages/core/src/lib/transitions/bifrost.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import { bifrostKernel } from './wasm/bifrost-kernel.js';
2 import { opacity } from '../../filters/native.js';
3 import { defineTransitionEffect, type PibblTransitionEffect } from './fade.js';
4 import { captureTransition, clamp, finite, regionSnapshot, type TransitionRegion } from './capture.js';
5
6 /**
7 * Color and motion controls for a bifrost visibility transition.
8 *
9 * @see {@link bifrost}
10 */
11 export interface PibblBifrostOptions {
12 /** Logical region over which the effect is evaluated. See {@link TransitionRegion}. */
13 readonly region: TransitionRegion;
14 /** Entry location or direction used by the transition. See {@link PibblBifrostOptions}. */
15 readonly entry?: Readonly<{
16 /**
17 * Horizontal coordinate or displacement in the containing coordinate system. See
18 * {@link PibblBifrostOptions}.
19 */
20 x: number;
21 /**
22 * Vertical coordinate or displacement in the containing coordinate system. See
23 * {@link PibblBifrostOptions}.
24 */
25 y: number;
26 }>;
27 /** Brightness control for the transition effect. See {@link PibblBifrostOptions}. */
28 readonly brightness?: number;
29 /** Hue control for the transition's emitted color. See {@link PibblBifrostOptions}. */
30 readonly hue?: number;
31 /** Amount of stretching applied by the transition. See {@link PibblBifrostOptions}. */
32 readonly stretch?: number;
33 /** Intensity of the electrical halo around the core. See {@link PibblBifrostOptions}. */
34 readonly glow?: number;
35 /**
36 * Seed used for deterministic sampling or simulation initialization. See
37 * {@link PibblBifrostOptions}.
38 */
39 readonly seed?: number;
40 }
41 /**
42 * A prismatic beam stretches a white-hot captured subject toward its entry point.
43 *
44 * @param options - Bifrost appearance and animation settings. See {@link PibblBifrostOptions}.
45 * @returns A bifrost transition effect. See {@link PibblTransitionEffect}.
46 *
47 * @see {@link PibblBifrostOptions}
48 * @see {@link PibblTransitionEffect}
49 */
50 export function bifrost(options: PibblBifrostOptions): PibblTransitionEffect {
51 const r = regionSnapshot(options.region), ax = r.x + r.width / 2, ay = r.y + r.height;
52 const ex = finite(options.entry?.x ?? ax, 'entry.x'), ey = finite(options.entry?.y ?? r.y - r.height * 2, 'entry.y');
53 const brightness = finite(options.brightness ?? 1, 'brightness', 0), hue = finite(options.hue ?? 0, 'hue');
54 const stretchAmount = finite(options.stretch ?? 7, 'stretch', 0), aura = finite(options.glow ?? 1, 'glow', 0);
55 const samplePatches = bifrostKernel(finite(options.seed ?? 0, 'seed'));
56 const distance = Math.hypot(ex - ax, ey - ay), ux = distance ? (ex - ax) / distance : 0, uy = distance ? (ey - ay) / distance : -1;
57 return defineTransitionEffect((q, phase, frame) => {
58 if (q === 1) return [];
59 if (q === 0) return [opacity({ amount: 0 })];
60 return [captureTransition(({ context: c, windows, drawSource, drawTint }) => {
61 const power = Math.pow(Math.sin(Math.PI * q), .65) * brightness * Math.pow(clamp((1 - q) / .32), 2);
62 const color = (h: number, a: number, light = 80) => `hsla(${h + hue},100%,${light}%,${clamp(a)})`;
63 const glow = (x: number, y: number, rx: number, ry: number, a: number) => {
64 c.save(); c.translate(x, y); c.scale(rx, ry);
65 const gradient = c.createRadialGradient(0, 0, 0, 0, 0, 1); gradient.addColorStop(0, `rgba(255,255,255,${clamp(a)})`); gradient.addColorStop(.25, color(230, a * .5)); gradient.addColorStop(1, color(230, 0));
66 c.fillStyle = gradient; c.fillRect(-1, -1, 2, 2); c.restore();
67 };
68 // Seeded, feathered clouds stretched along the transit axis. Unequal
69 // lengths, widths, hues and velocities avoid a fixed rainbow comb.
70 c.save(); c.globalCompositeOperation = 'lighter';
71 const dx = ax - ex, dy = ay - ey;
72 const angle = Math.atan2(dy, dx), seconds = frame.elapsed / 1000;
73 const flow = phase === 'enter' ? 1 : -1;
74 // End the beam at the impact plane, as the original ribbons did.
75 c.translate(ex, ey); c.rotate(angle);
76 c.beginPath(); c.rect(-Math.max(1, distance), -r.width * 2, Math.max(1, distance) * 2, r.width * 4); c.clip();
77 c.rotate(-angle); c.translate(-ex, -ey);
78 const patches = samplePatches(seconds, flow, power, r.width, distance, ex, ey, dx, dy);
79 for (let i = 0; i < 56; i++) {
80 const offset = i * 6;
81 const length = patches[offset + 2]!, width = patches[offset + 3]!, h = patches[offset + 4]!, alpha = patches[offset + 5]!;
82 c.save(); c.translate(patches[offset]!, patches[offset + 1]!);
83 c.rotate(angle); c.scale(length, width);
84 const patch = c.createRadialGradient(0, 0, 0, 0, 0, 1);
85 patch.addColorStop(0, color(h, alpha, 78));
86 patch.addColorStop(.25, color(h + 12, alpha * .8, 72));
87 patch.addColorStop(.6, color(h - 8, alpha * .3, 68));
88 patch.addColorStop(1, color(h, 0, 68));
89 c.fillStyle = patch; c.fillRect(-1, -1, 2, 2); c.restore();
90 }
91 c.restore();
92 const softness = 1 - clamp(q / .56), stretch = stretchAmount * softness * softness;
93 const travel = r.height * .7 * Math.pow(1 - clamp(q / .5), 2);
94 c.save(); c.translate(ax + ux * travel, ay + uy * travel);
95 c.transform(1 + stretch * ux * ux, stretch * ux * uy, stretch * ux * uy, 1 + stretch * uy * uy, 0, 0); c.translate(-ax, -ay);
96 c.globalAlpha = clamp(q / .075);
97 if (softness > 0) {
98 c.save(); c.globalAlpha *= clamp(brightness * .7); c.filter = `blur(${(22 + softness * 20) * aura * windows.density}px)`; drawTint('#e1e9ff'); c.restore();
99 c.filter = `blur(${(4 + softness * 10) * windows.density}px)`; drawTint('#fff');
100 } else {
101 drawSource(); c.globalAlpha *= clamp(clamp((1 - q) / .28) * brightness * 1.5); drawTint('#fff');
102 }
103 c.restore(); c.globalCompositeOperation = 'lighter';
104 const impact = Math.exp(-Math.pow((q - .56) / .085, 2));
105 glow(ax, r.y + r.height / 2, r.width, r.height * .8, impact * brightness * aura);
106 glow(ax, ay, r.width * .9, 24, power * aura);
107 })];
108 });
109 }
110
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.