@pixerate/flair 0.24.0 → 0.26.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.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -0
- package/dist/catalog/index.js +2 -2
- package/dist/{chunk-WAMA45G3.js → chunk-5USSDEQL.js} +77 -21
- package/dist/chunk-5USSDEQL.js.map +1 -0
- package/dist/{chunk-GTIAOYOK.js → chunk-74ZLCZOG.js} +13 -10
- package/dist/chunk-74ZLCZOG.js.map +1 -0
- package/dist/{chunk-NGKNI3QX.js → chunk-CK6EV37I.js} +19 -495
- package/dist/chunk-CK6EV37I.js.map +1 -0
- package/dist/chunk-DBTLCXKY.js +332 -0
- package/dist/chunk-DBTLCXKY.js.map +1 -0
- package/dist/chunk-G2B5L234.js +414 -0
- package/dist/chunk-G2B5L234.js.map +1 -0
- package/dist/{chunk-VP6IEWIK.js → chunk-H6L67KHI.js} +1 -1
- package/dist/chunk-H6VDFBIO.js +498 -0
- package/dist/chunk-H6VDFBIO.js.map +1 -0
- package/dist/{chunk-OQ4EWZ3B.js → chunk-HVXWNGTW.js} +83 -6
- package/dist/{chunk-OQ4EWZ3B.js.map → chunk-HVXWNGTW.js.map} +1 -1
- package/dist/chunk-JL6TI6II.js +724 -0
- package/dist/chunk-JL6TI6II.js.map +1 -0
- package/dist/{chunk-OHW7UG6D.js → chunk-ORAFCHX3.js} +26 -2
- package/dist/{chunk-OHW7UG6D.js.map → chunk-ORAFCHX3.js.map} +1 -1
- package/dist/{chunk-ARKC7LDW.js → chunk-RDU63CDO.js} +2 -331
- package/dist/chunk-RDU63CDO.js.map +1 -0
- package/dist/{chunk-LVPIIWPP.js → chunk-STKEKXCK.js} +18 -8
- package/dist/chunk-STKEKXCK.js.map +1 -0
- package/dist/chunk-UCZ43ARE.js +902 -0
- package/dist/chunk-UCZ43ARE.js.map +1 -0
- package/dist/chunk-YBGRXVAE.js +407 -0
- package/dist/chunk-YBGRXVAE.js.map +1 -0
- package/dist/cli.js +74 -11
- package/dist/cli.js.map +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +14 -12
- package/dist/editor/index.js.map +1 -1
- package/dist/effect-GGH3OSOT.js +47 -0
- package/dist/embed/index.d.ts +215 -0
- package/dist/embed/index.js +21 -0
- package/dist/{export-DH2HMACO.js → export-QVW474KM.js} +3 -3
- package/dist/export-QVW474KM.js.map +1 -0
- package/dist/frames-VEcqa40g.d.ts +161 -0
- package/dist/index.d.ts +47 -164
- package/dist/index.js +102 -80
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +167 -7
- package/dist/node.js +25 -3
- package/dist/react/index.d.ts +33 -2
- package/dist/react/index.js +54 -9
- package/dist/react/index.js.map +1 -1
- package/dist/recorder-CDPPTJMG.js +31 -0
- package/dist/recorder-CDPPTJMG.js.map +1 -0
- package/dist/{renderer-C2P1qGzP.d.ts → renderer-teaa98RE.d.ts} +1 -1
- package/dist/svelte/index.d.ts +33 -4
- package/dist/svelte/index.js +51 -9
- package/dist/svelte/index.js.map +1 -1
- package/dist/vector/index.d.ts +52 -1
- package/dist/vector/index.js +8 -4
- package/package.json +1 -1
- package/dist/chunk-ARKC7LDW.js.map +0 -1
- package/dist/chunk-GTIAOYOK.js.map +0 -1
- package/dist/chunk-LVPIIWPP.js.map +0 -1
- package/dist/chunk-NGKNI3QX.js.map +0 -1
- package/dist/chunk-UP6QQVRS.js +0 -331
- package/dist/chunk-UP6QQVRS.js.map +0 -1
- package/dist/chunk-WAMA45G3.js.map +0 -1
- package/dist/recorder-HKFFHQES.js +0 -10
- /package/dist/{chunk-VP6IEWIK.js.map → chunk-H6L67KHI.js.map} +0 -0
- /package/dist/{export-DH2HMACO.js.map → effect-GGH3OSOT.js.map} +0 -0
- /package/dist/{recorder-HKFFHQES.js.map → embed/index.js.map} +0 -0
|
@@ -1,393 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CAMERA_MS,
|
|
3
2
|
CAMERA_STEP_MS,
|
|
3
|
+
CAMERA_TRANSITIONS,
|
|
4
4
|
EFFECT_TYPES,
|
|
5
5
|
MAX_TILT,
|
|
6
6
|
cameraAt,
|
|
7
7
|
cameraTrack,
|
|
8
8
|
sceneStage
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5USSDEQL.js";
|
|
10
|
+
import {
|
|
11
|
+
ShaderFrames
|
|
12
|
+
} from "./chunk-H6VDFBIO.js";
|
|
10
13
|
import {
|
|
11
14
|
HOVER_SECONDS,
|
|
12
15
|
MAX_CLICKS,
|
|
13
16
|
MAX_TRAIL,
|
|
14
|
-
PointerTracker,
|
|
15
17
|
SVG_EDGE,
|
|
16
|
-
ShaderCompileError,
|
|
17
18
|
TRAIL_SECONDS,
|
|
18
19
|
TextureCache,
|
|
19
20
|
VERTEX_SOURCE,
|
|
20
|
-
clicksUniform,
|
|
21
21
|
compileShader,
|
|
22
22
|
createContext,
|
|
23
23
|
drawFrame,
|
|
24
24
|
resetGlCaches,
|
|
25
|
-
trailUniform,
|
|
26
25
|
wrapSource
|
|
27
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-RDU63CDO.js";
|
|
28
27
|
import {
|
|
29
28
|
controlValue,
|
|
30
29
|
imageUrls,
|
|
31
30
|
parseControls
|
|
32
31
|
} from "./chunk-VYOMXE6I.js";
|
|
33
32
|
|
|
34
|
-
// src/shader/renderer.ts
|
|
35
|
-
var ShaderRenderer = class {
|
|
36
|
-
canvas;
|
|
37
|
-
gl;
|
|
38
|
-
alpha;
|
|
39
|
-
shader = null;
|
|
40
|
-
textures = null;
|
|
41
|
-
/** Live sources by image control, kept to set again on a restored context. */
|
|
42
|
-
live = /* @__PURE__ */ new Map();
|
|
43
|
-
controls = [];
|
|
44
|
-
values;
|
|
45
|
-
source = "";
|
|
46
|
-
raf = 0;
|
|
47
|
-
lastNow = 0;
|
|
48
|
-
frame = 0;
|
|
49
|
-
fpsFrames = 0;
|
|
50
|
-
fpsSince = 0;
|
|
51
|
-
ownsCanvas = false;
|
|
52
|
-
resizeObserver;
|
|
53
|
-
intersectionObserver;
|
|
54
|
-
wasPlayingBeforeHidden = false;
|
|
55
|
-
hasEnteredViewport = false;
|
|
56
|
-
mouse = [0, 0, 0, 0];
|
|
57
|
-
/** `iPointer` and `iClicks`. */
|
|
58
|
-
pointer = new PointerTracker();
|
|
59
|
-
disposed = false;
|
|
60
|
-
/** The latest source's status. */
|
|
61
|
-
status = { ok: true, errors: [] };
|
|
62
|
-
/** `iTime`, in seconds. Use `seek` to change it. */
|
|
63
|
-
time = 0;
|
|
64
|
-
/** Playback speed: 1 is real time. */
|
|
65
|
-
speed = 1;
|
|
66
|
-
/** Device pixels per CSS pixel; lower renders faster. Use `setPixelRatio` to change it. */
|
|
67
|
-
pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
|
68
|
-
playing = true;
|
|
69
|
-
fps = 0;
|
|
70
|
-
onStats;
|
|
71
|
-
/** Called with `true` when the GL context is lost and `false` once it is restored and recompiled. */
|
|
72
|
-
onContextLost;
|
|
73
|
-
/** Called when an image control's URL (or live video) cannot be loaded; the control shows black. */
|
|
74
|
-
onImageError;
|
|
75
|
-
/**
|
|
76
|
-
* Creates a canvas in `target` (with `className` or options), or attaches to an existing
|
|
77
|
-
* `HTMLCanvasElement`, and starts the animation loop. Size the canvas with CSS.
|
|
78
|
-
*/
|
|
79
|
-
constructor(target, options) {
|
|
80
|
-
const opts = typeof options === "string" ? { className: options } : options ?? {};
|
|
81
|
-
let canvas;
|
|
82
|
-
if (target instanceof HTMLCanvasElement || "tagName" in target && target.tagName === "CANVAS") {
|
|
83
|
-
canvas = target;
|
|
84
|
-
if (opts.className) canvas.className = opts.className;
|
|
85
|
-
} else {
|
|
86
|
-
canvas = document.createElement("canvas");
|
|
87
|
-
if (opts.className) canvas.className = opts.className;
|
|
88
|
-
target.appendChild(canvas);
|
|
89
|
-
this.ownsCanvas = true;
|
|
90
|
-
}
|
|
91
|
-
this.canvas = canvas;
|
|
92
|
-
this.alpha = Boolean(opts.alpha);
|
|
93
|
-
this.gl = createContext(canvas, true, this.alpha);
|
|
94
|
-
this.textures = this.gl && this.textureCache(this.gl);
|
|
95
|
-
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
96
|
-
this.resizeObserver.observe(canvas);
|
|
97
|
-
const needsObserver = (opts.pauseWhenHidden || opts.playOnEnter) && typeof IntersectionObserver !== "undefined";
|
|
98
|
-
if (needsObserver) {
|
|
99
|
-
this.intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
100
|
-
if (!entry) return;
|
|
101
|
-
if (entry.isIntersecting) {
|
|
102
|
-
if (opts.playOnEnter && !this.hasEnteredViewport) {
|
|
103
|
-
this.hasEnteredViewport = true;
|
|
104
|
-
if (opts.autoplay ?? true) {
|
|
105
|
-
this.play();
|
|
106
|
-
}
|
|
107
|
-
} else if (this.wasPlayingBeforeHidden) {
|
|
108
|
-
this.wasPlayingBeforeHidden = false;
|
|
109
|
-
this.play();
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
if (this.playing && opts.pauseWhenHidden) {
|
|
113
|
-
this.wasPlayingBeforeHidden = true;
|
|
114
|
-
this.pause();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
this.intersectionObserver.observe(canvas);
|
|
119
|
-
}
|
|
120
|
-
const shouldAutoplay = (opts.autoplay ?? true) && !opts.playOnEnter;
|
|
121
|
-
this.playing = shouldAutoplay;
|
|
122
|
-
canvas.addEventListener("webglcontextlost", this.onLost);
|
|
123
|
-
canvas.addEventListener("webglcontextrestored", this.onRestored);
|
|
124
|
-
canvas.addEventListener("pointerdown", this.onPointerDown);
|
|
125
|
-
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
126
|
-
canvas.addEventListener("pointerenter", this.onPointerEnter);
|
|
127
|
-
canvas.addEventListener("pointerleave", this.onPointerLeave);
|
|
128
|
-
window.addEventListener("pointerup", this.onPointerUp);
|
|
129
|
-
this.resize();
|
|
130
|
-
this.raf = requestAnimationFrame(this.tick);
|
|
131
|
-
}
|
|
132
|
-
/** False when the browser has no WebGL2. */
|
|
133
|
-
get supported() {
|
|
134
|
-
return this.gl !== null;
|
|
135
|
-
}
|
|
136
|
-
/** Compiles a new source; returns its status. Keeps the previous program when it fails. */
|
|
137
|
-
setSource(source) {
|
|
138
|
-
this.source = source;
|
|
139
|
-
const controls = parseControls(source);
|
|
140
|
-
if (!this.gl) {
|
|
141
|
-
this.status = {
|
|
142
|
-
ok: false,
|
|
143
|
-
errors: [{ line: 0, message: "WebGL2 is not available in this browser." }]
|
|
144
|
-
};
|
|
145
|
-
return this.status;
|
|
146
|
-
}
|
|
147
|
-
const result = compileShader(this.gl, source, controls);
|
|
148
|
-
if (result.ok) {
|
|
149
|
-
if (this.shader) this.gl.deleteProgram(this.shader.program);
|
|
150
|
-
this.shader = result.shader;
|
|
151
|
-
this.controls = controls;
|
|
152
|
-
this.status = { ok: true, errors: [] };
|
|
153
|
-
} else {
|
|
154
|
-
this.status = { ok: false, errors: result.errors };
|
|
155
|
-
}
|
|
156
|
-
this.syncTextures();
|
|
157
|
-
if (!this.playing) this.render();
|
|
158
|
-
return this.status;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Sets control values (see `controlValue`); a paused preview redraws.
|
|
162
|
-
* Images load in the background and show once they arrive: await
|
|
163
|
-
* `ready()` before a `capture` that needs them.
|
|
164
|
-
*/
|
|
165
|
-
setValues(values) {
|
|
166
|
-
this.values = values;
|
|
167
|
-
this.syncTextures();
|
|
168
|
-
if (!this.playing) this.render();
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Shows a `<video>` (or a canvas) in the image control `name` (the
|
|
172
|
-
* uniform's name, such as `uImage`) in place of its URL, frame by frame;
|
|
173
|
-
* `null` goes back to the URL. The preview follows the video even while
|
|
174
|
-
* the shader's own clock is paused, and the video plays, seeks and loops
|
|
175
|
-
* on its own: the renderer never controls it. A video from another site
|
|
176
|
-
* needs `crossOrigin = "anonymous"` and CORS, or `onImageError` hears why
|
|
177
|
-
* it shows black. The value stays as it is, so documents, presets and
|
|
178
|
-
* agents keep seeing the URL.
|
|
179
|
-
*/
|
|
180
|
-
setLiveSource(name, source) {
|
|
181
|
-
if (source) this.live.set(name, source);
|
|
182
|
-
else this.live.delete(name);
|
|
183
|
-
this.textures?.setLive(name, source);
|
|
184
|
-
if (!this.playing) this.render();
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Settles once the images the current controls show have loaded, and
|
|
188
|
-
* every live video has a frame. Rejects with an {@link ImageLoadError}
|
|
189
|
-
* when one cannot be (it shows black).
|
|
190
|
-
*/
|
|
191
|
-
async ready() {
|
|
192
|
-
await Promise.all([
|
|
193
|
-
this.textures?.ready(imageUrls(this.controls, this.values)),
|
|
194
|
-
this.textures?.liveReady()
|
|
195
|
-
]);
|
|
196
|
-
}
|
|
197
|
-
play() {
|
|
198
|
-
this.playing = true;
|
|
199
|
-
this.emit();
|
|
200
|
-
}
|
|
201
|
-
pause() {
|
|
202
|
-
this.playing = false;
|
|
203
|
-
this.emit();
|
|
204
|
-
}
|
|
205
|
-
/** Back to `iTime` 0 and frame 0. */
|
|
206
|
-
restart() {
|
|
207
|
-
this.time = 0;
|
|
208
|
-
this.pointer.clear();
|
|
209
|
-
this.frame = 0;
|
|
210
|
-
this.render();
|
|
211
|
-
this.emit();
|
|
212
|
-
}
|
|
213
|
-
/** Jumps to `time` seconds (not below 0) and draws it. */
|
|
214
|
-
seek(time) {
|
|
215
|
-
if (time < this.time) this.pointer.clear();
|
|
216
|
-
this.time = Math.max(0, time);
|
|
217
|
-
this.render();
|
|
218
|
-
this.emit();
|
|
219
|
-
}
|
|
220
|
-
setPixelRatio(ratio) {
|
|
221
|
-
this.pixelRatio = ratio;
|
|
222
|
-
this.resize();
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Captures the current frame (optionally at another time) as a data URL
|
|
226
|
-
* (JPEG unless `type` says PNG), at most `maxEdge` pixels on the long side
|
|
227
|
-
* (default 1024). Playback time is left as it was. Null when nothing has
|
|
228
|
-
* compiled yet.
|
|
229
|
-
*/
|
|
230
|
-
capture(options = {}) {
|
|
231
|
-
if (!this.gl || !this.shader) return null;
|
|
232
|
-
const saved = this.time;
|
|
233
|
-
if (options.time !== void 0) this.time = Math.max(0, options.time);
|
|
234
|
-
this.render();
|
|
235
|
-
const { width, height } = this.canvas;
|
|
236
|
-
const scale = Math.min(1, (options.maxEdge ?? 1024) / Math.max(width, height));
|
|
237
|
-
const out = document.createElement("canvas");
|
|
238
|
-
out.width = Math.max(1, Math.round(width * scale));
|
|
239
|
-
out.height = Math.max(1, Math.round(height * scale));
|
|
240
|
-
out.getContext("2d").drawImage(this.canvas, 0, 0, out.width, out.height);
|
|
241
|
-
this.time = saved;
|
|
242
|
-
if (options.time !== void 0) this.render();
|
|
243
|
-
return out.toDataURL(options.type ?? "image/jpeg", 0.85);
|
|
244
|
-
}
|
|
245
|
-
/** Stops the loop, releases the context and removes the canvas. The renderer cannot be used again. */
|
|
246
|
-
dispose() {
|
|
247
|
-
this.disposed = true;
|
|
248
|
-
cancelAnimationFrame(this.raf);
|
|
249
|
-
this.resizeObserver.disconnect();
|
|
250
|
-
this.intersectionObserver?.disconnect();
|
|
251
|
-
this.canvas.removeEventListener("webglcontextlost", this.onLost);
|
|
252
|
-
this.canvas.removeEventListener("webglcontextrestored", this.onRestored);
|
|
253
|
-
this.canvas.removeEventListener("pointerdown", this.onPointerDown);
|
|
254
|
-
this.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
255
|
-
this.canvas.removeEventListener("pointerenter", this.onPointerEnter);
|
|
256
|
-
this.canvas.removeEventListener("pointerleave", this.onPointerLeave);
|
|
257
|
-
window.removeEventListener("pointerup", this.onPointerUp);
|
|
258
|
-
if (this.gl && this.shader) this.gl.deleteProgram(this.shader.program);
|
|
259
|
-
this.textures?.dispose();
|
|
260
|
-
this.textures = null;
|
|
261
|
-
this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
262
|
-
this.gl = null;
|
|
263
|
-
if (this.ownsCanvas) this.canvas.remove();
|
|
264
|
-
}
|
|
265
|
-
resize() {
|
|
266
|
-
const w = Math.max(1, Math.round(this.canvas.clientWidth * this.pixelRatio));
|
|
267
|
-
const h = Math.max(1, Math.round(this.canvas.clientHeight * this.pixelRatio));
|
|
268
|
-
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
269
|
-
this.canvas.width = w;
|
|
270
|
-
this.canvas.height = h;
|
|
271
|
-
this.render();
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
render(delta = 0) {
|
|
275
|
-
if (!this.gl || !this.shader || this.gl.isContextLost()) return;
|
|
276
|
-
drawFrame(
|
|
277
|
-
this.gl,
|
|
278
|
-
this.shader,
|
|
279
|
-
this.controls,
|
|
280
|
-
this.values,
|
|
281
|
-
{
|
|
282
|
-
width: this.canvas.width,
|
|
283
|
-
height: this.canvas.height,
|
|
284
|
-
time: this.time,
|
|
285
|
-
delta,
|
|
286
|
-
frame: this.frame,
|
|
287
|
-
mouse: this.mouse,
|
|
288
|
-
pointer: this.pointer.pointer(this.time),
|
|
289
|
-
clicks: this.pointer.clicksAt(this.time),
|
|
290
|
-
trail: this.pointer.trailAt(this.time)
|
|
291
|
-
},
|
|
292
|
-
this.textures ?? void 0
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
textureCache(gl) {
|
|
296
|
-
const cache = new TextureCache(gl, (_url, error) => {
|
|
297
|
-
if (error) this.onImageError?.(error);
|
|
298
|
-
else if (!this.playing) this.render();
|
|
299
|
-
});
|
|
300
|
-
for (const [name, source] of this.live) cache.setLive(name, source);
|
|
301
|
-
return cache;
|
|
302
|
-
}
|
|
303
|
-
/** Starts loading the images now shown, and frees the ones no longer shown. */
|
|
304
|
-
syncTextures() {
|
|
305
|
-
if (!this.textures) return;
|
|
306
|
-
const urls = imageUrls(this.controls, this.values);
|
|
307
|
-
this.textures.retain(urls);
|
|
308
|
-
for (const url of urls) this.textures.request(url);
|
|
309
|
-
}
|
|
310
|
-
tick = (now) => {
|
|
311
|
-
if (this.disposed) return;
|
|
312
|
-
this.raf = requestAnimationFrame(this.tick);
|
|
313
|
-
const delta = this.lastNow ? Math.min((now - this.lastNow) / 1e3, 0.1) : 0;
|
|
314
|
-
this.lastNow = now;
|
|
315
|
-
if (this.playing) {
|
|
316
|
-
this.time += delta * this.speed;
|
|
317
|
-
this.frame += 1;
|
|
318
|
-
this.render(delta * this.speed);
|
|
319
|
-
this.fpsFrames += 1;
|
|
320
|
-
} else if (this.textures?.liveChanged()) {
|
|
321
|
-
this.render();
|
|
322
|
-
}
|
|
323
|
-
if (now - this.fpsSince >= 500) {
|
|
324
|
-
this.fps = this.playing ? Math.round(this.fpsFrames * 1e3 / (now - this.fpsSince || 1)) : 0;
|
|
325
|
-
this.fpsFrames = 0;
|
|
326
|
-
this.fpsSince = now;
|
|
327
|
-
this.emit();
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
emit() {
|
|
331
|
-
this.onStats?.({
|
|
332
|
-
time: this.time,
|
|
333
|
-
fps: this.fps,
|
|
334
|
-
playing: this.playing,
|
|
335
|
-
width: this.canvas.width,
|
|
336
|
-
height: this.canvas.height
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
/** Pointer position in shader pixels (origin bottom-left, like gl_FragCoord). */
|
|
340
|
-
toPixels(e) {
|
|
341
|
-
const rect = this.canvas.getBoundingClientRect();
|
|
342
|
-
return [
|
|
343
|
-
(e.clientX - rect.left) * (this.canvas.width / rect.width),
|
|
344
|
-
(rect.bottom - e.clientY) * (this.canvas.height / rect.height)
|
|
345
|
-
];
|
|
346
|
-
}
|
|
347
|
-
onPointerDown = (e) => {
|
|
348
|
-
const [x, y] = this.toPixels(e);
|
|
349
|
-
this.mouse = [x, y, x, y];
|
|
350
|
-
this.pointer.down(x, y, this.time);
|
|
351
|
-
if (!this.playing) this.render();
|
|
352
|
-
};
|
|
353
|
-
onPointerMove = (e) => {
|
|
354
|
-
const [x, y] = this.toPixels(e);
|
|
355
|
-
this.pointer.move(x, y, this.time);
|
|
356
|
-
if (this.mouse[2] <= 0 || e.buttons === 0) {
|
|
357
|
-
if (!this.playing) this.render();
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
this.mouse = [x, y, this.mouse[2], this.mouse[3]];
|
|
361
|
-
if (!this.playing) this.render();
|
|
362
|
-
};
|
|
363
|
-
onPointerEnter = (e) => {
|
|
364
|
-
this.pointer.move(...this.toPixels(e), this.time);
|
|
365
|
-
this.pointer.hover(true, this.time);
|
|
366
|
-
};
|
|
367
|
-
onPointerLeave = () => {
|
|
368
|
-
this.pointer.hover(false, this.time);
|
|
369
|
-
};
|
|
370
|
-
onPointerUp = () => {
|
|
371
|
-
this.pointer.up(this.time);
|
|
372
|
-
if (this.mouse[2] > 0)
|
|
373
|
-
this.mouse = [this.mouse[0], this.mouse[1], -this.mouse[2], -Math.abs(this.mouse[3])];
|
|
374
|
-
};
|
|
375
|
-
onLost = (e) => {
|
|
376
|
-
e.preventDefault();
|
|
377
|
-
this.shader = null;
|
|
378
|
-
this.textures?.dispose();
|
|
379
|
-
this.textures = null;
|
|
380
|
-
this.onContextLost?.(true);
|
|
381
|
-
};
|
|
382
|
-
onRestored = () => {
|
|
383
|
-
resetGlCaches();
|
|
384
|
-
this.gl = createContext(this.canvas, true, this.alpha);
|
|
385
|
-
this.textures = this.gl && this.textureCache(this.gl);
|
|
386
|
-
this.setSource(this.source);
|
|
387
|
-
this.onContextLost?.(false);
|
|
388
|
-
};
|
|
389
|
-
};
|
|
390
|
-
|
|
391
33
|
// src/shader/thumbnails.ts
|
|
392
34
|
var ThumbnailPool = class {
|
|
393
35
|
renderWidth;
|
|
@@ -623,126 +265,6 @@ function registerThumbnail(canvas, source, values, onStatus) {
|
|
|
623
265
|
return sharedThumbnailPool().register(canvas, source, values, onStatus);
|
|
624
266
|
}
|
|
625
267
|
|
|
626
|
-
// src/shader/frames.ts
|
|
627
|
-
var ShaderFrames = class {
|
|
628
|
-
canvas;
|
|
629
|
-
width;
|
|
630
|
-
height;
|
|
631
|
-
controls;
|
|
632
|
-
gl;
|
|
633
|
-
shader;
|
|
634
|
-
textures;
|
|
635
|
-
values;
|
|
636
|
-
rendered = 0;
|
|
637
|
-
lastTime = null;
|
|
638
|
-
pixels;
|
|
639
|
-
constructor(options) {
|
|
640
|
-
const { source, values, width, height } = options;
|
|
641
|
-
this.width = Math.max(1, Math.round(width));
|
|
642
|
-
this.height = Math.max(1, Math.round(height));
|
|
643
|
-
this.canvas = options.canvas ?? document.createElement("canvas");
|
|
644
|
-
this.canvas.width = this.width;
|
|
645
|
-
this.canvas.height = this.height;
|
|
646
|
-
this.values = values;
|
|
647
|
-
const gl = createContext(this.canvas, true, Boolean(options.alpha));
|
|
648
|
-
if (!gl) throw new Error("WebGL2 is not available in this browser.");
|
|
649
|
-
this.gl = gl;
|
|
650
|
-
this.controls = parseControls(source);
|
|
651
|
-
const compiled = compileShader(gl, source, this.controls);
|
|
652
|
-
if (!compiled.ok) {
|
|
653
|
-
this.dispose();
|
|
654
|
-
throw new ShaderCompileError(compiled.errors);
|
|
655
|
-
}
|
|
656
|
-
this.shader = compiled.shader;
|
|
657
|
-
this.textures = new TextureCache(gl);
|
|
658
|
-
this.syncTextures();
|
|
659
|
-
this.pixels = new Uint8Array(this.width * this.height * 4);
|
|
660
|
-
}
|
|
661
|
-
/** Changes control values for the frames that follow. */
|
|
662
|
-
setValues(values) {
|
|
663
|
-
this.values = values;
|
|
664
|
-
this.syncTextures();
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* Draws a `<video>` (or a canvas) in the image control `name` instead of
|
|
668
|
-
* its URL, at the frame it is on when `render` runs; `null` goes back to
|
|
669
|
-
* the URL. See `ShaderRenderer.setLiveSource`.
|
|
670
|
-
*/
|
|
671
|
-
setLiveSource(name, source) {
|
|
672
|
-
this.textures.setLive(name, source);
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* Settles once every image the controls show has loaded, and every live
|
|
676
|
-
* video has a frame. Rejects with an {@link ImageLoadError} when one
|
|
677
|
-
* cannot be.
|
|
678
|
-
*/
|
|
679
|
-
async ready() {
|
|
680
|
-
await Promise.all([this.textures.ready(imageUrls(this.controls, this.values)), this.textures.liveReady()]);
|
|
681
|
-
}
|
|
682
|
-
/** Draws the frame at `time` seconds onto `canvas`. */
|
|
683
|
-
render(time, options = {}) {
|
|
684
|
-
const gl = this.context();
|
|
685
|
-
const delta = options.delta ?? (this.lastTime === null ? 0 : time - this.lastTime);
|
|
686
|
-
drawFrame(
|
|
687
|
-
gl,
|
|
688
|
-
this.shader,
|
|
689
|
-
this.controls,
|
|
690
|
-
this.values,
|
|
691
|
-
{
|
|
692
|
-
width: this.width,
|
|
693
|
-
height: this.height,
|
|
694
|
-
time,
|
|
695
|
-
delta,
|
|
696
|
-
frame: options.frame ?? this.rendered,
|
|
697
|
-
mouse: options.mouse ?? [0, 0, 0, 0],
|
|
698
|
-
pointer: options.pointer,
|
|
699
|
-
clicks: options.clicks && clicksUniform(options.clicks, time),
|
|
700
|
-
trail: options.trail && trailUniform(options.trail, time)
|
|
701
|
-
},
|
|
702
|
-
this.textures
|
|
703
|
-
);
|
|
704
|
-
this.rendered += 1;
|
|
705
|
-
this.lastTime = time;
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* The last rendered frame as RGBA bytes, rows top down (as in `ImageData`;
|
|
709
|
-
* WebGL itself reads bottom up). Pass `out` to reuse a buffer of
|
|
710
|
-
* `width * height * 4` bytes.
|
|
711
|
-
*/
|
|
712
|
-
readPixels(out) {
|
|
713
|
-
const gl = this.context();
|
|
714
|
-
const { width, height } = this;
|
|
715
|
-
const target = out ?? new Uint8Array(width * height * 4);
|
|
716
|
-
if (target.length !== width * height * 4) {
|
|
717
|
-
throw new RangeError(`readPixels needs a buffer of ${width * height * 4} bytes.`);
|
|
718
|
-
}
|
|
719
|
-
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
|
|
720
|
-
const row = width * 4;
|
|
721
|
-
for (let y = 0; y < height; y++) {
|
|
722
|
-
target.set(this.pixels.subarray((height - 1 - y) * row, (height - y) * row), y * row);
|
|
723
|
-
}
|
|
724
|
-
return target;
|
|
725
|
-
}
|
|
726
|
-
/** Releases the GL context now rather than at garbage collection (browsers cap live contexts). */
|
|
727
|
-
dispose() {
|
|
728
|
-
if (!this.gl) return;
|
|
729
|
-
if (this.shader) this.gl.deleteProgram(this.shader.program);
|
|
730
|
-
this.textures?.dispose();
|
|
731
|
-
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
732
|
-
this.gl = null;
|
|
733
|
-
}
|
|
734
|
-
syncTextures() {
|
|
735
|
-
const urls = imageUrls(this.controls, this.values);
|
|
736
|
-
this.textures.retain(urls);
|
|
737
|
-
for (const url of urls) this.textures.request(url);
|
|
738
|
-
}
|
|
739
|
-
context() {
|
|
740
|
-
if (!this.gl) throw new Error("These frames have been disposed.");
|
|
741
|
-
if (this.gl.isContextLost()) throw new Error("The graphics context was lost.");
|
|
742
|
-
return this.gl;
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
|
|
746
268
|
// src/shader/gif.ts
|
|
747
269
|
var yieldToBrowser = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
748
270
|
async function recordGif(options) {
|
|
@@ -1229,6 +751,10 @@ function validateScene(scene) {
|
|
|
1229
751
|
say(shotNum, "Camera perspective is the lens distance as a multiple of the stage width, 0.3 or more (1.2 by default).");
|
|
1230
752
|
if (cam.motionBlur !== void 0 && !(typeof cam.motionBlur === "number" && cam.motionBlur >= 0 && cam.motionBlur <= 3))
|
|
1231
753
|
say(shotNum, "Camera motionBlur is 0 (none) to 3 (a whip); 1 is natural.");
|
|
754
|
+
if (cam.transition !== void 0 && !CAMERA_TRANSITIONS.includes(cam.transition))
|
|
755
|
+
say(shotNum, `Camera transition is one of ${CAMERA_TRANSITIONS.map((t) => `"${t}"`).join(", ")}; leave it out to move.`);
|
|
756
|
+
else if (idx === 0 && (cam.transition === "whip" || cam.transition === "zoom"))
|
|
757
|
+
say(shotNum, `The first shot has no framing to ${cam.transition} from, so its camera starts in place; move the transition to a later shot or leave it out.`);
|
|
1232
758
|
}
|
|
1233
759
|
if (shot.hold !== void 0 && shot.hold < 0) {
|
|
1234
760
|
say(shotNum, "Shot hold cannot be negative.");
|
|
@@ -1282,13 +808,13 @@ function planShots(scene, stage) {
|
|
|
1282
808
|
const steps = Math.round(entry.moveMs / CAMERA_STEP_MS);
|
|
1283
809
|
const move = Array.from({ length: Math.max(0, steps - 1) }, (_, i) => cameraAt(entry, (i + 1) * CAMERA_STEP_MS, stage));
|
|
1284
810
|
move.push(entry.to);
|
|
1285
|
-
return { move, elements: entry.elements };
|
|
811
|
+
return { move, moveMs: entry.moveMs, elements: entry.elements };
|
|
1286
812
|
});
|
|
1287
813
|
}
|
|
1288
814
|
function standaloneSceneHtml(scene) {
|
|
1289
815
|
const stage = sceneStage(scene);
|
|
1290
816
|
const serializedScene = JSON.stringify(scene).replace(/</g, "\\u003c");
|
|
1291
|
-
const serializedPlan = JSON.stringify({ stage,
|
|
817
|
+
const serializedPlan = JSON.stringify({ stage, shots: planShots(scene, stage) }).replace(/</g, "\\u003c");
|
|
1292
818
|
const title = scene.title || scene.name;
|
|
1293
819
|
return `<!DOCTYPE html>
|
|
1294
820
|
<html lang="en">
|
|
@@ -1550,8 +1076,8 @@ function standaloneSceneHtml(scene) {
|
|
|
1550
1076
|
|
|
1551
1077
|
// The camera at time 'now': a step of the current shot's move, or where it settled.
|
|
1552
1078
|
function cameraNow(now) {
|
|
1553
|
-
const move = plan.shots[currentShotIdx]
|
|
1554
|
-
const u = Math.min(1, (now - moveFrom) /
|
|
1079
|
+
const { move, moveMs } = plan.shots[currentShotIdx];
|
|
1080
|
+
const u = moveMs > 0 ? Math.min(1, (now - moveFrom) / moveMs) : 1;
|
|
1555
1081
|
return move[Math.min(move.length - 1, Math.floor(u * move.length))];
|
|
1556
1082
|
}
|
|
1557
1083
|
|
|
@@ -1603,10 +1129,10 @@ function standaloneSceneHtml(scene) {
|
|
|
1603
1129
|
|
|
1604
1130
|
// Playing moves on to the next shot once this one's move and hold are over.
|
|
1605
1131
|
const shot = scene.shots[currentShotIdx];
|
|
1606
|
-
if (isPlaying && now - shotStartTime > plan.
|
|
1132
|
+
if (isPlaying && now - shotStartTime > plan.shots[currentShotIdx].moveMs + (shot.hold ?? 800) && currentShotIdx < scene.shots.length - 1) {
|
|
1607
1133
|
goTo(currentShotIdx + 1, true);
|
|
1608
1134
|
}
|
|
1609
|
-
if (isPlaying || now - moveFrom < plan.
|
|
1135
|
+
if (isPlaying || now - moveFrom < plan.shots[currentShotIdx].moveMs) {
|
|
1610
1136
|
requestAnimationFrame(draw);
|
|
1611
1137
|
}
|
|
1612
1138
|
}
|
|
@@ -1615,7 +1141,7 @@ function standaloneSceneHtml(scene) {
|
|
|
1615
1141
|
function goTo(idx, animate) {
|
|
1616
1142
|
currentShotIdx = idx;
|
|
1617
1143
|
shotStartTime = performance.now();
|
|
1618
|
-
moveFrom = animate ? shotStartTime : shotStartTime - plan.
|
|
1144
|
+
moveFrom = animate ? shotStartTime : shotStartTime - plan.shots[idx].moveMs;
|
|
1619
1145
|
updateShotUI();
|
|
1620
1146
|
requestAnimationFrame(draw);
|
|
1621
1147
|
}
|
|
@@ -1625,7 +1151,7 @@ function standaloneSceneHtml(scene) {
|
|
|
1625
1151
|
playBtn.textContent = isPlaying ? 'Pause' : 'Play';
|
|
1626
1152
|
// The current shot counts as settled, so its hold runs from now.
|
|
1627
1153
|
if (isPlaying) {
|
|
1628
|
-
shotStartTime = performance.now() - plan.
|
|
1154
|
+
shotStartTime = performance.now() - plan.shots[currentShotIdx].moveMs;
|
|
1629
1155
|
requestAnimationFrame(draw);
|
|
1630
1156
|
}
|
|
1631
1157
|
});
|
|
@@ -1649,11 +1175,9 @@ function escapeHtml2(str) {
|
|
|
1649
1175
|
}
|
|
1650
1176
|
|
|
1651
1177
|
export {
|
|
1652
|
-
ShaderRenderer,
|
|
1653
1178
|
ThumbnailPool,
|
|
1654
1179
|
sharedThumbnailPool,
|
|
1655
1180
|
registerThumbnail,
|
|
1656
|
-
ShaderFrames,
|
|
1657
1181
|
recordGif,
|
|
1658
1182
|
standaloneHtml,
|
|
1659
1183
|
ALL_BLEND_MODES,
|
|
@@ -1662,4 +1186,4 @@ export {
|
|
|
1662
1186
|
validateScene,
|
|
1663
1187
|
standaloneSceneHtml
|
|
1664
1188
|
};
|
|
1665
|
-
//# sourceMappingURL=chunk-
|
|
1189
|
+
//# sourceMappingURL=chunk-CK6EV37I.js.map
|