Skip to content

packages/core/src/lib/transitions/hologram.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

1 import { lazyTransitionKernel } from './wasm/kernel.js';
2 import { bytes } from './wasm/hologram-bytes.js';
3 const createKernel = lazyTransitionKernel(bytes);
4 import { opacity } from '../../filters/native.js';
5 import { defineTransitionEffect, type PibblTransitionEffect } from './fade.js';
6 import { captureTransition, clamp, finite, regionSnapshot, type TransitionRegion } from './capture.js';
7 
8 /**
9  * Color, scanning, and distortion controls for a hologram visibility transition.
10  *
11  * @see {@link hologram}
12  */
13 export interface PibblHologramOptions {
14   /** Logical region over which the effect is evaluated. See {@link TransitionRegion}. */
15   readonly region: TransitionRegion;
16   /** Entry location or direction used by the transition. See {@link PibblHologramOptions}. */
17   readonly entry?: Readonly<{
18     /**
19      * Horizontal coordinate or displacement in the containing coordinate system. See
20      * {@link PibblHologramOptions}.
21      */
22     x: number;
23     /**
24      * Vertical coordinate or displacement in the containing coordinate system. See
25      * {@link PibblHologramOptions}.
26      */
27     y: number;
28   }>;
29   /** Hue control for the transition's emitted color. See {@link PibblHologramOptions}. */
30   readonly hue?: number;
31   /** Brightness control for the transition effect. See {@link PibblHologramOptions}. */
32   readonly brightness?: number;
33   /** Fog intensity used by the hologram effect. See {@link PibblHologramOptions}. */
34   readonly fog?: number;
35   /** Distortion intensity used by the hologram effect. See {@link PibblHologramOptions}. */
36   readonly glitch?: number;
37   /**
38    * Seed used for deterministic sampling or simulation initialization. See
39    * {@link PibblHologramOptions}.
40    */
41   readonly seed?: number;
42 }
43 /**
44  * A diffuse scan reconstructs the captured subject while interference settles.
45  *
46  * @param options - Hologram appearance and animation settings. See {@link PibblHologramOptions}.
47  * @returns A hologram transition effect. See {@link PibblTransitionEffect}.
48  *
49  * @see {@link PibblHologramOptions}
50  * @see {@link PibblTransitionEffect}
51  */
52 export function hologram(options: PibblHologramOptions): PibblTransitionEffect {
53   const r = regionSnapshot(options.region);
54   const entry = { x: finite(options.entry?.x ?? r.x + r.width / 2, 'entry.x'), y: finite(options.entry?.y ?? r.y + r.height, 'entry.y') };
55   const hue = finite(options.hue ?? 183, 'hue'), brightness = finite(options.brightness ?? 1, 'brightness', 0);
56   const fog = finite(options.fog ?? 1, 'fog', 0), glitch = finite(options.glitch ?? 1, 'glitch', 0);
57   const kernel = createKernel(finite(options.seed ?? 0, 'seed'));
58   return defineTransitionEffect(q => {
59     if (q === 1) return [];
60     if (q === 0) return [opacity({ amount: 0 })];
61     return [captureTransition(({ context: c, windows, drawSource, drawTint }) => {
62       const { exports, values } = kernel();
63       exports.sample!(q, brightness, fog, glitch, r.x, r.y, r.width, r.height, entry.x, entry.y);
64       const y = values[0]!, power = values[1]!;
65       const color = (a: number, light = 75) => `hsla(${hue},100%,${light}%,${clamp(a)})`;
66       const glow = (x: number, cy: number, rx: number, ry: number, a: number) => {
67         c.save(); c.translate(x, cy); c.scale(rx, ry);
68         const g = c.createRadialGradient(0, 0, 0, 0, 0, 1); g.addColorStop(0, color(a, 92)); g.addColorStop(.3, color(a * .5)); g.addColorStop(1, color(0));
69         c.fillStyle = g; c.fillRect(-1, -1, 2, 2); c.restore();
70       };
71       c.save(); c.beginPath(); c.rect(r.x, y, r.width, r.height * q); c.clip();
72       drawSource(); c.globalAlpha = clamp((1 - q) * 1.8); drawTint(color(1)); c.globalAlpha = 1;
73       c.globalCompositeOperation = 'source-atop';
74       c.fillStyle = color(power * .18);
75       const lineCount = Math.min(512, Math.ceil(r.height / 5));
76       for (let i = 0; i < lineCount; i++) c.fillRect(r.x, r.y + i * r.height / lineCount, r.width, 1);
77       c.restore();
78       // Transient strips copy only captured pixels, preserving gaps in the subject.
79       c.save(); c.beginPath(); c.rect(r.x, y, r.width, r.height * q); c.clip();
80       for (let i = 0; i < 9; i++) {
81         const offset = 2 + i * 4, pulse = values[offset]!;
82         if (pulse < .03) continue;
83         c.save(); c.beginPath(); c.rect(r.x, values[offset + 1]!, r.width, values[offset + 2]!); c.clip();
84         c.translate(values[offset + 3]!, 0); c.globalAlpha = clamp(pulse); drawTint(color(1, 92)); c.restore();
85       }
86       c.restore(); c.globalCompositeOperation = 'lighter';
87       for (let i = 0; i < 25; i++) {
88         c.strokeStyle = color(values[38 + i]!); c.lineWidth = 1;
89         c.beginPath(); c.moveTo(entry.x, entry.y); c.lineTo(r.x + i / 24 * r.width, y); c.stroke();
90       }
91       for (let i = 0; i < 24; i++) {
92         const offset = 63 + i * 5;
93         glow(values[offset]!, values[offset + 1]!, values[offset + 2]!, values[offset + 3]!, values[offset + 4]!);
94       }
95       glow(r.x + r.width / 2, y, r.width * .6, 28, power * .5);
96       c.save(); c.filter = `blur(${9 * windows.density}px)`; c.strokeStyle = color(power * .65, 87); c.lineWidth = 7;
97       c.beginPath(); c.ellipse(r.x + r.width / 2, y, r.width * .5, 10, 0, 0, Math.PI * 2); c.stroke();
98       c.filter = `blur(${3 * windows.density}px)`; c.lineWidth = 3; c.stroke(); c.restore();
99     })];
100   });
101 }
102 

Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.