@framefields/node-compositor 0.0.0-stage → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +22 -0
  2. package/.turbo/turbo-test.log +21 -0
  3. package/CHANGELOG.md +32 -0
  4. package/LICENCE +202 -0
  5. package/SKILL.md +370 -0
  6. package/dist/index-B0ir9Z7o.d.mts +139 -0
  7. package/dist/index-B0ir9Z7o.d.mts.map +1 -0
  8. package/dist/index.d.mts +230 -0
  9. package/dist/index.d.mts.map +1 -0
  10. package/dist/index.mjs +674 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/dist/renderer.d.mts +2 -0
  13. package/dist/renderer.mjs +3 -0
  14. package/dist/renderers-DoYYCzgA.mjs +3326 -0
  15. package/dist/renderers-DoYYCzgA.mjs.map +1 -0
  16. package/package.json +51 -3
  17. package/src/index.ts +2 -0
  18. package/src/renderers/audio-processor.ts +123 -0
  19. package/src/renderers/container-bg.test.ts +22 -0
  20. package/src/renderers/container-bg.ts +15 -0
  21. package/src/renderers/index.ts +9 -0
  22. package/src/renderers/transform.test.ts +120 -0
  23. package/src/renderers/transform.ts +101 -0
  24. package/src/renderers/transform3d.test.ts +290 -0
  25. package/src/renderers/transform3d.ts +428 -0
  26. package/src/renderers/webgpu-renderer.ts +4038 -0
  27. package/src/shared/audio-reactive.test.ts +224 -0
  28. package/src/shared/bezier-solver.test.ts +92 -0
  29. package/src/shared/bezier-solver.ts +257 -0
  30. package/src/shared/compiler.test.ts +1309 -0
  31. package/src/shared/compiler.ts +996 -0
  32. package/src/shared/config.ts +44 -0
  33. package/src/shared/index.ts +3 -0
  34. package/src/shared/kinetic-typography.test.ts +138 -0
  35. package/src/shared/presets.test.ts +217 -0
  36. package/src/shared/presets.ts +644 -0
  37. package/src/shared/processor.ts +105 -0
  38. package/tsconfig.json +13 -0
  39. package/tsdown.config.ts +13 -0
  40. package/README.md +0 -3
@@ -0,0 +1,3326 @@
1
+ import { decodeAudioSource, resolveLayerDuration } from "@framefields/compositions";
2
+ import { collectNodeOps, computeLayout, evaluateTrackAtFrame, initLayout } from "@framefields/compositions/program";
3
+ import { DEFAULT_DURATION_MS, getActiveMediaMetadata, reportRenderDiagnostic } from "@framefields/core";
4
+ import gsapModule from "gsap";
5
+ import { audioRegistry } from "@framefields/node-sdk";
6
+ import { defineRenderer } from "@framefields/node-sdk/renderer";
7
+ import { resolveMediaSourceUrl } from "@framefields/client-utils";
8
+ import { measureText } from "@framefields/renderers";
9
+ import { TemporalDeflickerPipeline } from "@framefields/tensor-webgpu";
10
+ import { AudioLatentTrackCache, Camera3D, Matrix4Math, Renderer3D, ScreenSpaceRelightPipeline, SlugFontCache, Vector3Math, generateExtrudedTextGeometry, jointMatrices, measureMaxCaptionHeight, parseColor, poseNodes, srtLoader } from "@framefields/webgpu-renderers";
11
+
12
+ //#region src/shared/bezier-solver.ts
13
+ /**
14
+ * Solves a cubic bezier curve defined by control points (x1, y1) and (x2, y2)
15
+ * for a given progress parameter t in [0, 1].
16
+ */
17
+ function solveCubicBezier(x1, y1, x2, y2, t) {
18
+ if (t <= 0) return 0;
19
+ if (t >= 1) return 1;
20
+ if (x1 === y1 && x2 === y2) return t;
21
+ let u = t;
22
+ for (let i = 0; i < 8; i++) {
23
+ const oneMinusU$1 = 1 - u;
24
+ const oneMinusU2 = oneMinusU$1 * oneMinusU$1;
25
+ const u2 = u * u;
26
+ const diff = 3 * oneMinusU2 * u * x1 + 3 * oneMinusU$1 * u2 * x2 + u2 * u - t;
27
+ if (Math.abs(diff) < 1e-6) break;
28
+ const dx = 3 * oneMinusU2 * x1 + 6 * oneMinusU$1 * u * (x2 - x1) + 3 * u2 * (1 - x2);
29
+ if (Math.abs(dx) < 1e-6) break;
30
+ u -= diff / dx;
31
+ u = Math.max(0, Math.min(1, u));
32
+ }
33
+ const oneMinusU = 1 - u;
34
+ const checkX = 3 * oneMinusU * oneMinusU * u * x1 + 3 * oneMinusU * u * u * x2 + u * u * u;
35
+ if (Math.abs(checkX - t) > .001) {
36
+ let low = 0;
37
+ let high = 1;
38
+ u = t;
39
+ for (let i = 0; i < 16; i++) {
40
+ const mid = (low + high) * .5;
41
+ const oMid = 1 - mid;
42
+ const xMid = 3 * oMid * oMid * mid * x1 + 3 * oMid * mid * mid * x2 + mid * mid * mid;
43
+ if (Math.abs(xMid - t) < 1e-5) {
44
+ u = mid;
45
+ break;
46
+ }
47
+ if (xMid < t) low = mid;
48
+ else high = mid;
49
+ u = mid;
50
+ }
51
+ }
52
+ const oU = 1 - u;
53
+ return 3 * oU * oU * u * y1 + 3 * oU * u * u * y2 + u * u * u;
54
+ }
55
+
56
+ //#endregion
57
+ //#region src/shared/compiler.ts
58
+ const gsap = gsapModule.timeline ? gsapModule : gsapModule.default || gsapModule;
59
+ const cache = /* @__PURE__ */ new Map();
60
+ const nodesHashMemo = /* @__PURE__ */ new WeakMap();
61
+ /** Entries untouched for this long are dropped (and their timeline killed). */
62
+ const CACHE_TTL_MS = 1e4;
63
+ /** Max compiled timelines retained per cache. */
64
+ const TIMELINE_CACHE_MAX = 50;
65
+ const LAYER_TIMELINE_CACHE_MAX = 50;
66
+ function fnv1a(str) {
67
+ let hash = 2166136261;
68
+ for (let i = 0; i < str.length; i++) {
69
+ hash ^= str.charCodeAt(i);
70
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
71
+ }
72
+ return (hash >>> 0).toString(16);
73
+ }
74
+ const signalIds = /* @__PURE__ */ new WeakMap();
75
+ let nextSignalId = 0;
76
+ function signalsKey(signals) {
77
+ if (!signals || typeof signals !== "object") return stableStringify(signals);
78
+ return Object.keys(signals).sort().map((name) => {
79
+ const s = signals[name];
80
+ if (s && typeof s === "object" && typeof s.get === "function") {
81
+ let id = signalIds.get(s);
82
+ if (id === void 0) {
83
+ id = nextSignalId++;
84
+ signalIds.set(s, id);
85
+ }
86
+ return `${JSON.stringify(name)}:#${id}`;
87
+ }
88
+ return `${JSON.stringify(name)}:${stableStringify(s)}`;
89
+ }).join(",");
90
+ }
91
+ /**
92
+ * Paused gsap timelines stay attached to gsap's global timeline until killed,
93
+ * so anything dropped from a cache must be killed or it is never collected.
94
+ */
95
+ function release(value) {
96
+ value.tl.kill();
97
+ }
98
+ /**
99
+ * Make room for one insertion: drop and kill TTL-expired entries, then evict
100
+ * the least-recently-used entry if the cache is at capacity. Every dropped
101
+ * timeline MUST be killed — see `release`.
102
+ */
103
+ function evictForInsert(cache$1, maxSize) {
104
+ const now = Date.now();
105
+ for (const [key, entry] of cache$1.entries()) if (now - entry.cacheTime > CACHE_TTL_MS) {
106
+ cache$1.delete(key);
107
+ release(entry.value);
108
+ }
109
+ if (cache$1.size >= maxSize) {
110
+ const oldestKey = cache$1.keys().next().value;
111
+ if (oldestKey !== void 0) {
112
+ const oldest = cache$1.get(oldestKey);
113
+ cache$1.delete(oldestKey);
114
+ if (oldest) release(oldest.value);
115
+ }
116
+ }
117
+ }
118
+ function stableStringify(obj) {
119
+ if (obj === null) return "null";
120
+ if (obj === void 0) return "undefined";
121
+ if (typeof obj !== "object") return JSON.stringify(obj);
122
+ if (ArrayBuffer.isView(obj)) return `[TypedArray:${obj.byteLength}]`;
123
+ if (Array.isArray(obj)) return "[" + obj.map(stableStringify).join(",") + "]";
124
+ return "{" + Object.keys(obj).sort().map((key) => {
125
+ return `${JSON.stringify(key)}:${stableStringify(obj[key])}`;
126
+ }).join(",") + "}";
127
+ }
128
+ function getCachedTimeline(cacheKey) {
129
+ const entry = cache.get(cacheKey);
130
+ if (entry) {
131
+ if (Date.now() - entry.cacheTime > CACHE_TTL_MS) {
132
+ cache.delete(cacheKey);
133
+ release(entry.value);
134
+ return;
135
+ }
136
+ entry.cacheTime = Date.now();
137
+ cache.delete(cacheKey);
138
+ cache.set(cacheKey, entry);
139
+ return entry.value;
140
+ }
141
+ }
142
+ function setCachedTimeline(cacheKey, value) {
143
+ evictForInsert(cache, TIMELINE_CACHE_MAX);
144
+ cache.set(cacheKey, {
145
+ cacheTime: Date.now(),
146
+ value
147
+ });
148
+ }
149
+ function mappedEase(ease, durationFrames, fps = 24) {
150
+ if (!ease) return "none";
151
+ const { name, dir, params } = ease;
152
+ if (name === "none") return "none";
153
+ if (name === "spring") {
154
+ const damping = Math.max(.001, params?.[0] ?? 10);
155
+ const stiffness = Math.max(.001, params?.[1] ?? 100);
156
+ const mass = Math.max(.001, params?.[2] ?? 1);
157
+ return (p) => {
158
+ const t = p * (durationFrames && durationFrames > 0 ? durationFrames / (fps > 0 ? fps : 24) : 1);
159
+ const m = mass;
160
+ const k = stiffness;
161
+ const c = damping;
162
+ const w0 = Math.sqrt(k / m);
163
+ const zeta = c / (2 * Math.sqrt(k * m));
164
+ let x = 0;
165
+ if (zeta < 1) {
166
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
167
+ const A = -1;
168
+ const B = -zeta * w0 / wd;
169
+ x = Math.exp(-zeta * w0 * t) * (A * Math.cos(wd * t) + B * Math.sin(wd * t));
170
+ } else if (zeta === 1) {
171
+ const A = -1;
172
+ const B = -w0;
173
+ x = Math.exp(-w0 * t) * (A + B * t);
174
+ } else {
175
+ const r1 = -w0 * (zeta - Math.sqrt(zeta * zeta - 1));
176
+ const r2 = -w0 * (zeta + Math.sqrt(zeta * zeta - 1));
177
+ const C1 = -1 / (1 - r1 / r2);
178
+ const C2 = -1 - C1;
179
+ x = C1 * Math.exp(r1 * t) + C2 * Math.exp(r2 * t);
180
+ }
181
+ return 1 + x;
182
+ };
183
+ }
184
+ if (name === "cubic") {
185
+ const x1 = params?.[0] ?? .25;
186
+ const y1 = params?.[1] ?? .1;
187
+ const x2 = params?.[2] ?? .25;
188
+ const y2 = params?.[3] ?? 1;
189
+ return (p) => solveCubicBezier(x1, y1, x2, y2, p);
190
+ }
191
+ if (name === "hold") return (p) => p >= .9999 ? 1 : 0;
192
+ const easeName = name;
193
+ let suffix = "";
194
+ if (params && params.length > 0) suffix = `(${params.join(", ")})`;
195
+ else if (name === "back") suffix = "(1.7)";
196
+ else if (name === "elastic") suffix = "(1, 0.3)";
197
+ return `${easeName}.${dir}${suffix}`;
198
+ }
199
+ function compileTrackToTimeline(tl, stub, track, startFrame, fps, durationFrames, signals) {
200
+ const prop = track.prop;
201
+ if ((track.source ?? { type: "keyframe" }).type !== "keyframe") {
202
+ const endF = Number.isFinite(durationFrames) ? durationFrames : Math.round(fps * 5);
203
+ const frameStepSec = 1 / fps;
204
+ const kfList = [];
205
+ const firstVal = evaluateTrackAtFrame(track, startFrame, fps, {
206
+ startFrame,
207
+ baseValues: stub,
208
+ signals
209
+ });
210
+ stub[prop] = firstVal;
211
+ kfList.push({
212
+ [prop]: firstVal,
213
+ duration: 0
214
+ });
215
+ for (let f = 1; f <= endF; f++) {
216
+ const val = evaluateTrackAtFrame(track, startFrame + f, fps, {
217
+ startFrame,
218
+ baseValues: stub,
219
+ signals
220
+ });
221
+ kfList.push({
222
+ [prop]: val,
223
+ duration: frameStepSec,
224
+ ease: "none"
225
+ });
226
+ }
227
+ tl.to(stub, {
228
+ keyframes: kfList,
229
+ repeat: track.repeat ?? 0,
230
+ yoyo: !!track.yoyo,
231
+ lazy: false,
232
+ overwrite: false,
233
+ immediateRender: false
234
+ }, startFrame / fps);
235
+ return {
236
+ dropped: 0,
237
+ maxAuthoredFrame: 0
238
+ };
239
+ }
240
+ const sortedKeyframes = [...track.keyframes].sort((a, b) => a.frame - b.frame);
241
+ const validKeyframes = sortedKeyframes.filter((kf) => kf.frame >= 0 && kf.frame <= durationFrames);
242
+ const dropped = sortedKeyframes.length - validKeyframes.length;
243
+ const maxAuthoredFrame = dropped > 0 ? sortedKeyframes[sortedKeyframes.length - 1].frame : 0;
244
+ if (validKeyframes.length === 0) return {
245
+ dropped,
246
+ maxAuthoredFrame
247
+ };
248
+ if (prop === "hidden" || prop === "muted") for (const kf of validKeyframes) {
249
+ const absoluteSec = (startFrame + kf.frame) / fps;
250
+ tl.set(stub, {
251
+ [prop]: kf.value,
252
+ immediateRender: false
253
+ }, absoluteSec);
254
+ }
255
+ else {
256
+ const repeat = track.repeat ?? 0;
257
+ const yoyo = !!track.yoyo;
258
+ if (validKeyframes.length === 1) {
259
+ const kf0 = validKeyframes[0];
260
+ const absoluteSec = (startFrame + kf0.frame) / fps;
261
+ tl.set(stub, {
262
+ [prop]: kf0.value,
263
+ immediateRender: false
264
+ }, absoluteSec);
265
+ } else if (validKeyframes.length === 2) {
266
+ const kf0 = validKeyframes[0];
267
+ const kf1 = validKeyframes[1];
268
+ const val0 = kf0.value;
269
+ const val1 = kf1.value;
270
+ const startSec = (startFrame + kf0.frame) / fps;
271
+ const durSec = (kf1.frame - kf0.frame) / fps;
272
+ const trackDurationFrames = track.durationFrames ?? kf1.frame - kf0.frame;
273
+ const easeVal = mappedEase(kf1.ease ?? kf0.ease, trackDurationFrames, fps);
274
+ if (val0 === stub[prop]) tl.to(stub, {
275
+ [prop]: val1,
276
+ duration: durSec,
277
+ ease: easeVal,
278
+ repeat,
279
+ yoyo,
280
+ lazy: false,
281
+ overwrite: false,
282
+ immediateRender: false
283
+ }, startSec);
284
+ else tl.fromTo(stub, { [prop]: val0 }, {
285
+ [prop]: val1,
286
+ duration: durSec,
287
+ ease: easeVal,
288
+ repeat,
289
+ yoyo,
290
+ lazy: false,
291
+ overwrite: false,
292
+ immediateRender: false
293
+ }, startSec);
294
+ } else {
295
+ const kf0 = validKeyframes[0];
296
+ const startSec = (startFrame + kf0.frame) / fps;
297
+ const kfList = [];
298
+ kfList.push({
299
+ [prop]: kf0.value,
300
+ duration: 0
301
+ });
302
+ for (let i = 1; i < validKeyframes.length; i++) {
303
+ const prevKf = validKeyframes[i - 1];
304
+ const currKf = validKeyframes[i];
305
+ const deltaFrames = currKf.frame - prevKf.frame;
306
+ const durSec = deltaFrames / fps;
307
+ const easeVal = mappedEase(currKf.ease, deltaFrames, fps);
308
+ kfList.push({
309
+ [prop]: currKf.value,
310
+ duration: durSec,
311
+ ease: easeVal
312
+ });
313
+ }
314
+ tl.to(stub, {
315
+ keyframes: kfList,
316
+ repeat,
317
+ yoyo,
318
+ lazy: false,
319
+ overwrite: false,
320
+ immediateRender: false
321
+ }, startSec);
322
+ }
323
+ }
324
+ return {
325
+ dropped,
326
+ maxAuthoredFrame
327
+ };
328
+ }
329
+ /**
330
+ * Dedupe per compiled cache key — a fresh render of the same broken doc SHOULD
331
+ * re-warn. Single-frame renders are the exception: each `renderFrame` call has
332
+ * its own `img-` render id, so probing a doc frame by frame would repeat the
333
+ * warning on every frame; those dedupe per document instead.
334
+ */
335
+ const truncationWarnedKeys = /* @__PURE__ */ new Set();
336
+ const TRUNCATION_WARNED_CAP = 100;
337
+ function warnTruncationOnce(cacheKey, truncations) {
338
+ if (truncationWarnedKeys.has(cacheKey)) return;
339
+ if (truncationWarnedKeys.size >= TRUNCATION_WARNED_CAP) truncationWarnedKeys.clear();
340
+ truncationWarnedKeys.add(cacheKey);
341
+ const details = truncations.map((t) => `'${t.prop}' (track '${t.trackId}') on '${t.layerId}': ${t.dropped} keyframe(s) past the clip window (last authored frame ${t.maxAuthoredFrame})`).join("; ");
342
+ console.warn(`[compositor] ${truncations.length} track(s) truncated — animation tails will not play: ${details}. Extend durationFrames or move those keyframes.`);
343
+ }
344
+ function hashNodes(nodeOps, width, height, fps, durationSec) {
345
+ return fnv1a(stableStringify({
346
+ nodes: nodeOps.map((vm) => {
347
+ const lop = vm?.operation || {};
348
+ return {
349
+ id: lop.id,
350
+ animation: lop.animation,
351
+ startFrame: lop.startFrame,
352
+ durationFrames: lop.durationFrames,
353
+ x: lop.x,
354
+ y: lop.y,
355
+ scale: lop.scale,
356
+ rotation: lop.rotation,
357
+ opacity: lop.opacity,
358
+ volume: lop.volume,
359
+ width: lop.width,
360
+ height: lop.height,
361
+ hidden: lop.hidden,
362
+ muted: lop.muted,
363
+ backgroundColor: lop.backgroundColor,
364
+ fill: lop.fill,
365
+ borderColor: lop.borderColor,
366
+ textBackgroundColor: lop.textBackgroundColor,
367
+ rotateX: lop.rotateX,
368
+ rotateY: lop.rotateY,
369
+ rotateZ: lop.rotateZ,
370
+ perspective: lop.perspective,
371
+ perspectiveOriginX: lop.perspectiveOriginX,
372
+ perspectiveOriginY: lop.perspectiveOriginY,
373
+ translateZ: lop.translateZ,
374
+ backfaceVisibility: lop.backfaceVisibility,
375
+ transformStyle: lop.transformStyle,
376
+ is3D: lop.is3D,
377
+ z: lop.z,
378
+ scaleZ: lop.scaleZ,
379
+ twoSided: lop.twoSided,
380
+ cameraX: lop.cameraX,
381
+ cameraY: lop.cameraY,
382
+ cameraZ: lop.cameraZ,
383
+ targetX: lop.targetX,
384
+ targetY: lop.targetY,
385
+ targetZ: lop.targetZ,
386
+ cameraPitch: lop.cameraPitch,
387
+ cameraYaw: lop.cameraYaw,
388
+ cameraRoll: lop.cameraRoll,
389
+ cameraFov: lop.cameraFov,
390
+ cameraZoom: lop.cameraZoom,
391
+ focalLength: lop.focalLength,
392
+ focusDistance: lop.focusDistance,
393
+ fStop: lop.fStop,
394
+ maxBlurRadius: lop.maxBlurRadius,
395
+ shakeTranslation: lop.shakeTranslation,
396
+ shakeRotation: lop.shakeRotation,
397
+ orbitRadius: lop.orbitRadius,
398
+ orbitAzimuth: lop.orbitAzimuth,
399
+ orbitElevation: lop.orbitElevation,
400
+ material: lop.material,
401
+ shininess: lop.shininess,
402
+ roughness: lop.roughness,
403
+ specularIntensity: lop.specularIntensity,
404
+ ambientIntensity: lop.ambientIntensity,
405
+ metallic: lop.metallic,
406
+ lightType: lop.lightType,
407
+ intensity: lop.intensity,
408
+ radius: lop.radius,
409
+ angle: lop.angle,
410
+ penumbra: lop.penumbra,
411
+ animationProgress: lop.animationProgress,
412
+ animationTime: lop.animationTime,
413
+ animationSpeed: lop.animationSpeed,
414
+ wireframe: lop.wireframe,
415
+ scaleX: lop.scaleX,
416
+ scaleY: lop.scaleY,
417
+ fontSize: lop.fontSize,
418
+ text: lop.text
419
+ };
420
+ }),
421
+ width,
422
+ height,
423
+ fps,
424
+ durationSec
425
+ }));
426
+ }
427
+ function compileTimeline(renderId, virtualMedia, options) {
428
+ const fps = options.fps;
429
+ const durationSec = options.durationSec;
430
+ const width = virtualMedia?.metadata?.width ?? 1920;
431
+ const height = virtualMedia?.metadata?.height ?? 1080;
432
+ const nodeOps = collectNodeOps(virtualMedia?.children ?? []);
433
+ const memoKey = virtualMedia?.children;
434
+ const memo = memoKey ? nodesHashMemo.get(memoKey) : void 0;
435
+ let nodesHash;
436
+ if (memo && memo.width === width && memo.height === height && memo.fps === fps && memo.durationSec === durationSec) nodesHash = memo.hash;
437
+ else {
438
+ nodesHash = hashNodes(nodeOps, width, height, fps, durationSec);
439
+ if (memoKey) nodesHashMemo.set(memoKey, {
440
+ width,
441
+ height,
442
+ fps,
443
+ durationSec,
444
+ hash: nodesHash
445
+ });
446
+ }
447
+ const irHash = fnv1a(`${nodesHash}|${signalsKey(virtualMedia?.operation?.signals ?? virtualMedia?.signals)}`);
448
+ const cacheKey = `${renderId}_${irHash}`;
449
+ const cached = getCachedTimeline(cacheKey);
450
+ if (cached) return cached;
451
+ const tl = gsap.timeline({
452
+ paused: true,
453
+ smoothChildTiming: false
454
+ });
455
+ const targetsById = {};
456
+ const signals = virtualMedia?.operation?.signals ?? virtualMedia?.signals ?? {};
457
+ nodeOps.forEach((vm, index) => {
458
+ const lop = vm?.operation || {};
459
+ const layerId = lop.id || lop.inputHandleId || `layer_${index}`;
460
+ const stub = {
461
+ id: layerId,
462
+ inputHandleId: lop.inputHandleId || `layer_${index}`,
463
+ trackId: layerId,
464
+ x: lop.kind === "camera" || lop.kind === "light" ? lop.x ?? lop.cameraX : lop.x ?? 0,
465
+ y: lop.kind === "camera" || lop.kind === "light" ? lop.y ?? lop.cameraY : lop.y ?? 0,
466
+ scale: lop.scale ?? 1,
467
+ rotation: lop.rotation ?? 0,
468
+ opacity: lop.opacity ?? 1,
469
+ volume: lop.volume ?? 1,
470
+ width: lop.width,
471
+ height: lop.height,
472
+ hidden: !!lop.hidden,
473
+ muted: !!lop.muted,
474
+ rotateX: lop.rotateX ?? 0,
475
+ rotateY: lop.rotateY ?? 0,
476
+ rotateZ: lop.rotateZ ?? 0,
477
+ perspective: lop.perspective ?? 0,
478
+ perspectiveOriginX: lop.perspectiveOriginX ?? .5,
479
+ perspectiveOriginY: lop.perspectiveOriginY ?? .5,
480
+ translateZ: lop.translateZ ?? 0,
481
+ backfaceVisibility: lop.backfaceVisibility ?? "visible",
482
+ transformStyle: lop.transformStyle ?? "flat",
483
+ fontSize: lop.fontSize,
484
+ text: 1,
485
+ fill: lop.fill,
486
+ fillColor: lop.fillColor ?? lop.fill,
487
+ strokeColor: lop.strokeColor ?? lop.stroke,
488
+ strokeWidth: lop.strokeWidth,
489
+ letterSpacing: lop.letterSpacing,
490
+ firstMargin: lop.firstMargin ?? lop.pathOptions?.firstMargin,
491
+ lastMargin: lop.lastMargin ?? lop.pathOptions?.lastMargin,
492
+ baselineShift: lop.baselineShift ?? lop.pathOptions?.baselineShift,
493
+ offset: lop.offset,
494
+ rangeStart: lop.rangeStart,
495
+ rangeEnd: lop.rangeEnd,
496
+ animators: lop.animators,
497
+ pathOptions: lop.pathOptions,
498
+ typewriter: lop.typewriter,
499
+ marquee: lop.marquee,
500
+ is3D: lop.is3D ?? (lop.kind === "camera" || lop.kind === "light" || lop.kind === "model3d" ? true : false),
501
+ z: lop.z ?? (lop.kind === "camera" ? lop.z ?? -1500 : 0),
502
+ scaleZ: lop.scaleZ ?? 1,
503
+ twoSided: lop.twoSided ?? true,
504
+ material: lop.material,
505
+ shininess: lop.shininess,
506
+ roughness: lop.roughness,
507
+ specularIntensity: lop.specularIntensity,
508
+ ambientIntensity: lop.ambientIntensity,
509
+ metallic: lop.metallic,
510
+ animationProgress: lop.animationProgress ?? 0,
511
+ animationTime: lop.animationTime ?? 0,
512
+ animationSpeed: lop.animationSpeed ?? 1,
513
+ wireframe: lop.wireframe ?? false,
514
+ scaleX: lop.scaleX ?? 1,
515
+ scaleY: lop.scaleY ?? 1,
516
+ cameraX: lop.cameraX ?? (lop.kind === "camera" ? lop.x : void 0),
517
+ cameraY: lop.cameraY ?? (lop.kind === "camera" ? lop.y : void 0),
518
+ cameraZ: lop.cameraZ ?? (lop.kind === "camera" ? lop.z : void 0),
519
+ targetX: lop.targetX,
520
+ targetY: lop.targetY,
521
+ targetZ: lop.targetZ,
522
+ cameraPitch: lop.cameraPitch ?? lop.pitch ?? 0,
523
+ cameraYaw: lop.cameraYaw ?? lop.yaw ?? 0,
524
+ cameraRoll: lop.cameraRoll ?? lop.roll ?? 0,
525
+ cameraFov: lop.cameraFov ?? lop.lens?.fov ?? 50,
526
+ cameraZoom: lop.cameraZoom ?? lop.lens?.zoom ?? 1,
527
+ focalLength: lop.focalLength ?? lop.lens?.focalLength ?? 50,
528
+ focusDistance: lop.focusDistance ?? lop.dof?.focusDistance,
529
+ fStop: lop.fStop ?? lop.dof?.fStop ?? 2.8,
530
+ maxBlurRadius: lop.maxBlurRadius ?? lop.dof?.maxBlurRadius ?? 24,
531
+ shakeTranslation: lop.shakeTranslation ?? (lop.shake?.enabled ? lop.shake?.translationAmplitude ?? 0 : 0),
532
+ shakeRotation: lop.shakeRotation ?? (lop.shake?.enabled ? lop.shake?.rotationAmplitude ?? 0 : 0),
533
+ orbitRadius: lop.orbitRadius,
534
+ orbitAzimuth: lop.orbitAzimuth,
535
+ orbitElevation: lop.orbitElevation,
536
+ lightType: lop.lightType,
537
+ intensity: lop.intensity ?? (lop.kind === "light" ? 1 : void 0),
538
+ radius: lop.radius,
539
+ angle: lop.angle,
540
+ penumbra: lop.penumbra,
541
+ decay: lop.decay
542
+ };
543
+ for (const track of lop.animation?.tracks ?? []) if ((track.source ?? { type: "keyframe" }).type !== "keyframe") stub[track.prop] = evaluateTrackAtFrame(track, lop.startFrame ?? 0, fps, {
544
+ startFrame: lop.startFrame ?? 0,
545
+ baseValues: stub,
546
+ signals
547
+ });
548
+ else if (track.keyframes && track.keyframes.length > 0) {
549
+ const kf0 = track.keyframes[0];
550
+ const seeded = stub;
551
+ if (kf0.frame === 0) seeded[track.prop] = kf0.value;
552
+ else if (seeded[track.prop] === void 0) seeded[track.prop] = lop[track.prop] ?? kf0.value;
553
+ }
554
+ targetsById[layerId] = stub;
555
+ });
556
+ const truncations = [];
557
+ nodeOps.forEach((vm) => {
558
+ const lop = vm?.operation || {};
559
+ const layerId = lop.id || lop.inputHandleId;
560
+ const stub = layerId ? targetsById[layerId] : void 0;
561
+ if (!stub) return;
562
+ const startFrame = lop.startFrame ?? 0;
563
+ let durationFrames = lop.durationFrames;
564
+ if (durationFrames === void 0) {
565
+ const child = vm.children?.[0];
566
+ const childMeta = child ? getActiveMediaMetadata(child) : null;
567
+ const composeDuration = virtualMedia?.metadata?.durationMs ?? DEFAULT_DURATION_MS;
568
+ const layerType = lop.type ?? (lop.kind === "text" ? "Text" : lop.kind === "media" ? child ? child.operation?.dataType : void 0 : void 0);
569
+ const durationMs = resolveLayerDuration(void 0, childMeta?.durationMs ?? void 0, composeDuration, layerType);
570
+ durationFrames = Math.max(1, Math.round(durationMs / 1e3 * fps));
571
+ }
572
+ for (const track of lop.animation?.tracks ?? []) {
573
+ const { dropped, maxAuthoredFrame } = compileTrackToTimeline(tl, stub, track, startFrame, fps, durationFrames, signals);
574
+ if (dropped > 0) truncations.push({
575
+ layerId,
576
+ trackId: track.id,
577
+ prop: track.prop,
578
+ dropped,
579
+ maxAuthoredFrame
580
+ });
581
+ }
582
+ });
583
+ if (truncations.length > 0) {
584
+ warnTruncationOnce(renderId.startsWith("img-") ? `frame_${irHash}` : cacheKey, truncations);
585
+ for (const t of truncations) reportRenderDiagnostic(renderId, {
586
+ severity: "warning",
587
+ code: "animation_truncated",
588
+ layerId: t.layerId,
589
+ message: `'${t.prop}' on '${t.layerId}': ${t.dropped} keyframe(s) past the clip window (last authored frame ${t.maxAuthoredFrame}) never play`
590
+ });
591
+ }
592
+ tl.seek(1e-6);
593
+ tl.seek(0);
594
+ const compileResult = {
595
+ tl,
596
+ targetsById,
597
+ nodes: nodeOps
598
+ };
599
+ setCachedTimeline(cacheKey, compileResult);
600
+ return compileResult;
601
+ }
602
+ const layerTimelineCache = /* @__PURE__ */ new Map();
603
+ function compileLayerTimeline(layerId, animation, baseVolume, baseMuted, fps) {
604
+ const cacheKey = `layer_${layerId}_${fnv1a(stableStringify({
605
+ animation,
606
+ baseVolume,
607
+ baseMuted,
608
+ fps
609
+ }))}`;
610
+ const cached = layerTimelineCache.get(cacheKey);
611
+ if (cached) {
612
+ if (Date.now() - cached.cacheTime <= CACHE_TTL_MS) {
613
+ cached.cacheTime = Date.now();
614
+ return cached.value;
615
+ }
616
+ layerTimelineCache.delete(cacheKey);
617
+ release(cached.value);
618
+ }
619
+ const tl = gsap.timeline({
620
+ paused: true,
621
+ smoothChildTiming: false
622
+ });
623
+ const stub = {
624
+ volume: baseVolume,
625
+ muted: baseMuted
626
+ };
627
+ const tracks = animation?.tracks ?? [];
628
+ for (const track of tracks) {
629
+ const prop = track.prop;
630
+ if (prop !== "volume" && prop !== "muted") continue;
631
+ compileTrackToTimeline(tl, stub, track, 0, fps, Infinity);
632
+ }
633
+ tl.seek(1e-6);
634
+ tl.seek(0);
635
+ const result = {
636
+ tl,
637
+ stub
638
+ };
639
+ evictForInsert(layerTimelineCache, LAYER_TIMELINE_CACHE_MAX);
640
+ layerTimelineCache.set(cacheKey, {
641
+ cacheTime: Date.now(),
642
+ value: result
643
+ });
644
+ return result;
645
+ }
646
+
647
+ //#endregion
648
+ //#region src/renderers/container-bg.ts
649
+ /**
650
+ * Which container kinds paint their own background.
651
+ *
652
+ * flex/block/box all accept an optional `background` in the program schema.
653
+ * The renderer must draw the rounded background pass for ALL of them (the old
654
+ * code only painted `box`, so flex/block backgrounds were silently dropped and
655
+ * their "transparent-optimized" fast path skipped the fill entirely).
656
+ */
657
+ function shouldPaintContainerBackground(kind, background) {
658
+ if (!background) return false;
659
+ return kind === "flex" || kind === "block" || kind === "box";
660
+ }
661
+
662
+ //#endregion
663
+ //#region src/renderers/transform.ts
664
+ function translate(tx, ty) {
665
+ return {
666
+ a: 1,
667
+ b: 0,
668
+ c: 0,
669
+ d: 1,
670
+ e: tx,
671
+ f: ty
672
+ };
673
+ }
674
+ /** Rotation in degrees (screen convention: positive = clockwise, y-down). */
675
+ function rotate(degrees) {
676
+ const rad = degrees * Math.PI / 180;
677
+ const cos = Math.cos(rad);
678
+ const sin = Math.sin(rad);
679
+ return {
680
+ a: cos,
681
+ b: sin,
682
+ c: -sin,
683
+ d: cos,
684
+ e: 0,
685
+ f: 0
686
+ };
687
+ }
688
+ function scale(sx, sy = sx) {
689
+ return {
690
+ a: sx,
691
+ b: 0,
692
+ c: 0,
693
+ d: sy,
694
+ e: 0,
695
+ f: 0
696
+ };
697
+ }
698
+ /** m1 ∘ m2 (m2 applied first — matches DOMMatrix.multiply ordering). */
699
+ function multiply(m1, m2) {
700
+ return {
701
+ a: m1.a * m2.a + m1.c * m2.b,
702
+ b: m1.b * m2.a + m1.d * m2.b,
703
+ c: m1.a * m2.c + m1.c * m2.d,
704
+ d: m1.b * m2.c + m1.d * m2.d,
705
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
706
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
707
+ };
708
+ }
709
+ /** Materialize into a DOMMatrix for the renderer's transform stack. */
710
+ function toDOMMatrix(m) {
711
+ return new DOMMatrix([
712
+ m.a,
713
+ m.b,
714
+ m.c,
715
+ m.d,
716
+ m.e,
717
+ m.f
718
+ ]);
719
+ }
720
+ /**
721
+ * Layer transform matrix with anchor-aware pivot semantics.
722
+ *
723
+ * The composition node box is drawn at (0, 0, width, height) in local space
724
+ * and positioned at `x`/`y`. `anchorX`/`anchorY` (0–1 fractions of the box)
725
+ * select the pivot point that scale/rotation are applied AROUND:
726
+ *
727
+ * position → pivot → rotate → scale → un-pivot
728
+ *
729
+ * With the default center anchor (0.5, 0.5) a `scale: 2` animation grows the
730
+ * node symmetrically from its middle instead of from its top-left corner.
731
+ * The pivot point itself is invariant under rotation and scale.
732
+ */
733
+ function buildLayerMatrix(params) {
734
+ const ax = params.anchorX ?? .5;
735
+ const ay = params.anchorY ?? .5;
736
+ const px = ax * params.width;
737
+ const py = ay * params.height;
738
+ return multiply(translate(params.x, params.y), multiply(translate(px, py), multiply(rotate(params.rotation), multiply(scale(params.scale), translate(-px, -py)))));
739
+ }
740
+
741
+ //#endregion
742
+ //#region src/renderers/transform3d.ts
743
+ const Transform3DMath = {
744
+ identity() {
745
+ return [
746
+ 1,
747
+ 0,
748
+ 0,
749
+ 0,
750
+ 0,
751
+ 1,
752
+ 0,
753
+ 0,
754
+ 0,
755
+ 0,
756
+ 1,
757
+ 0,
758
+ 0,
759
+ 0,
760
+ 0,
761
+ 1
762
+ ];
763
+ },
764
+ multiply(a, b) {
765
+ const out = new Array(16);
766
+ for (let row = 0; row < 4; row++) for (let col = 0; col < 4; col++) out[col * 4 + row] = a[0 + row] * b[col * 4 + 0] + a[4 + row] * b[col * 4 + 1] + a[8 + row] * b[col * 4 + 2] + a[12 + row] * b[col * 4 + 3];
767
+ return out;
768
+ },
769
+ translate(tx, ty, tz = 0) {
770
+ return [
771
+ 1,
772
+ 0,
773
+ 0,
774
+ 0,
775
+ 0,
776
+ 1,
777
+ 0,
778
+ 0,
779
+ 0,
780
+ 0,
781
+ 1,
782
+ 0,
783
+ tx,
784
+ ty,
785
+ tz,
786
+ 1
787
+ ];
788
+ },
789
+ scale(sx, sy, sz = 1) {
790
+ return [
791
+ sx,
792
+ 0,
793
+ 0,
794
+ 0,
795
+ 0,
796
+ sy,
797
+ 0,
798
+ 0,
799
+ 0,
800
+ 0,
801
+ sz,
802
+ 0,
803
+ 0,
804
+ 0,
805
+ 0,
806
+ 1
807
+ ];
808
+ },
809
+ rotateX(rad) {
810
+ const c = Math.cos(rad);
811
+ const s = Math.sin(rad);
812
+ return [
813
+ 1,
814
+ 0,
815
+ 0,
816
+ 0,
817
+ 0,
818
+ c,
819
+ s,
820
+ 0,
821
+ 0,
822
+ -s,
823
+ c,
824
+ 0,
825
+ 0,
826
+ 0,
827
+ 0,
828
+ 1
829
+ ];
830
+ },
831
+ rotateY(rad) {
832
+ const c = Math.cos(rad);
833
+ const s = Math.sin(rad);
834
+ return [
835
+ c,
836
+ 0,
837
+ -s,
838
+ 0,
839
+ 0,
840
+ 1,
841
+ 0,
842
+ 0,
843
+ s,
844
+ 0,
845
+ c,
846
+ 0,
847
+ 0,
848
+ 0,
849
+ 0,
850
+ 1
851
+ ];
852
+ },
853
+ rotateZ(rad) {
854
+ const c = Math.cos(rad);
855
+ const s = Math.sin(rad);
856
+ return [
857
+ c,
858
+ s,
859
+ 0,
860
+ 0,
861
+ -s,
862
+ c,
863
+ 0,
864
+ 0,
865
+ 0,
866
+ 0,
867
+ 1,
868
+ 0,
869
+ 0,
870
+ 0,
871
+ 0,
872
+ 1
873
+ ];
874
+ },
875
+ perspective(d) {
876
+ if (d <= 0) return Transform3DMath.identity();
877
+ const m = Transform3DMath.identity();
878
+ m[11] = -1 / d;
879
+ return m;
880
+ },
881
+ buildLayer3DMatrix(config) {
882
+ const { x, y, width, height, rotateXDeg = 0, rotateYDeg = 0, rotateZDeg = 0, scaleX = 1, scaleY = 1, perspectivePx = 0, originXRatio = .5, originYRatio = .5, translateZPx = 0 } = config;
883
+ const pivotX = x + width * originXRatio;
884
+ const pivotY = y + height * originYRatio;
885
+ const rxRad = rotateXDeg * Math.PI / 180;
886
+ const ryRad = rotateYDeg * Math.PI / 180;
887
+ const rzRad = rotateZDeg * Math.PI / 180;
888
+ let m = Transform3DMath.identity();
889
+ m = Transform3DMath.multiply(m, Transform3DMath.translate(pivotX, pivotY, 0));
890
+ if (perspectivePx > 0) m = Transform3DMath.multiply(m, Transform3DMath.perspective(perspectivePx));
891
+ if (translateZPx !== 0) m = Transform3DMath.multiply(m, Transform3DMath.translate(0, 0, translateZPx));
892
+ if (rzRad !== 0) m = Transform3DMath.multiply(m, Transform3DMath.rotateZ(rzRad));
893
+ if (ryRad !== 0) m = Transform3DMath.multiply(m, Transform3DMath.rotateY(ryRad));
894
+ if (rxRad !== 0) m = Transform3DMath.multiply(m, Transform3DMath.rotateX(rxRad));
895
+ m = Transform3DMath.multiply(m, Transform3DMath.translate(-width * originXRatio, -height * originYRatio, 0));
896
+ if (scaleX !== 1 || scaleY !== 1) m = Transform3DMath.multiply(m, Transform3DMath.scale(scaleX, scaleY, 1));
897
+ return m;
898
+ },
899
+ projectPoint(m, p) {
900
+ const x = p.x;
901
+ const y = p.y;
902
+ const z = p.z;
903
+ const X = m[0] * x + m[4] * y + m[8] * z + m[12];
904
+ const Y = m[1] * x + m[5] * y + m[9] * z + m[13];
905
+ const W = m[3] * x + m[7] * y + m[11] * z + m[15];
906
+ const wInv = Math.abs(W) > 1e-6 ? 1 / W : 1;
907
+ return {
908
+ x: X * wInv,
909
+ y: Y * wInv
910
+ };
911
+ },
912
+ projectRectangleCorners(matrix, width, height) {
913
+ return {
914
+ topLeft: Transform3DMath.projectPoint(matrix, {
915
+ x: 0,
916
+ y: 0,
917
+ z: 0
918
+ }),
919
+ topRight: Transform3DMath.projectPoint(matrix, {
920
+ x: width,
921
+ y: 0,
922
+ z: 0
923
+ }),
924
+ bottomLeft: Transform3DMath.projectPoint(matrix, {
925
+ x: 0,
926
+ y: height,
927
+ z: 0
928
+ }),
929
+ bottomRight: Transform3DMath.projectPoint(matrix, {
930
+ x: width,
931
+ y: height,
932
+ z: 0
933
+ })
934
+ };
935
+ },
936
+ computeSignedArea(quad) {
937
+ const { topLeft: p0, topRight: p1, bottomRight: p2, bottomLeft: p3 } = quad;
938
+ return .5 * (p0.x * p1.y - p1.x * p0.y + (p1.x * p2.y - p2.x * p1.y) + (p2.x * p3.y - p3.x * p2.y) + (p3.x * p0.y - p0.x * p3.y));
939
+ }
940
+ };
941
+ /**
942
+ * Computes an 8-parameter 3x3 homography matrix mapping destination coordinates (quad)
943
+ * to normalized source coordinates (unit square [0, 1]^2).
944
+ *
945
+ * The homography H maps destination (x, y) to source (u, v):
946
+ * u = (h00*x + h01*y + h02) / (h20*x + h21*y + h22)
947
+ * v = (h10*x + h11*y + h12) / (h20*x + h21*y + h22)
948
+ *
949
+ * Resulting array: [h00, h01, h02, h10, h11, h12, h20, h21, 1.0]
950
+ */
951
+ function solveHomography(dstPoints, srcPoints = [
952
+ {
953
+ x: 0,
954
+ y: 0
955
+ },
956
+ {
957
+ x: 1,
958
+ y: 0
959
+ },
960
+ {
961
+ x: 0,
962
+ y: 1
963
+ },
964
+ {
965
+ x: 1,
966
+ y: 1
967
+ }
968
+ ]) {
969
+ const A = [];
970
+ const B = [];
971
+ for (let i = 0; i < 4; i++) {
972
+ const xi = dstPoints[i]?.x ?? 0;
973
+ const yi = dstPoints[i]?.y ?? 0;
974
+ const ui = srcPoints[i]?.x ?? 0;
975
+ const vi = srcPoints[i]?.y ?? 0;
976
+ A.push([
977
+ xi,
978
+ yi,
979
+ 1,
980
+ 0,
981
+ 0,
982
+ 0,
983
+ -xi * ui,
984
+ -yi * ui
985
+ ]);
986
+ B.push(ui);
987
+ A.push([
988
+ 0,
989
+ 0,
990
+ 0,
991
+ xi,
992
+ yi,
993
+ 1,
994
+ -xi * vi,
995
+ -yi * vi
996
+ ]);
997
+ B.push(vi);
998
+ }
999
+ const n = 8;
1000
+ for (let i = 0; i < n; i++) {
1001
+ let maxRow = i;
1002
+ let maxVal = Math.abs(A[i][i]);
1003
+ for (let k = i + 1; k < n; k++) if (Math.abs(A[k][i]) > maxVal) {
1004
+ maxVal = Math.abs(A[k][i]);
1005
+ maxRow = k;
1006
+ }
1007
+ const tempRow = A[i];
1008
+ A[i] = A[maxRow];
1009
+ A[maxRow] = tempRow;
1010
+ const tempB = B[i];
1011
+ B[i] = B[maxRow];
1012
+ B[maxRow] = tempB;
1013
+ const pivot = A[i][i];
1014
+ if (Math.abs(pivot) < 1e-12) A[i][i] = pivot < 0 ? -1e-12 : 1e-12;
1015
+ for (let k = i + 1; k < n; k++) {
1016
+ const factor = A[k][i] / A[i][i];
1017
+ for (let j = i; j < n; j++) A[k][j] -= factor * A[i][j];
1018
+ B[k] -= factor * B[i];
1019
+ }
1020
+ }
1021
+ const h = new Array(8).fill(0);
1022
+ for (let i = n - 1; i >= 0; i--) {
1023
+ let sum = B[i];
1024
+ for (let j = i + 1; j < n; j++) sum -= A[i][j] * h[j];
1025
+ h[i] = sum / A[i][i];
1026
+ }
1027
+ return [
1028
+ h[0],
1029
+ h[1],
1030
+ h[2],
1031
+ h[3],
1032
+ h[4],
1033
+ h[5],
1034
+ h[6],
1035
+ h[7],
1036
+ 1
1037
+ ];
1038
+ }
1039
+ /**
1040
+ * Checks whether a layer operation or compiled stub activates the 3D perspective path.
1041
+ */
1042
+ function has3DTransform(target, lop) {
1043
+ const rx = target?.rotateX ?? lop?.rotateX ?? 0;
1044
+ const ry = target?.rotateY ?? lop?.rotateY ?? 0;
1045
+ const rz = target?.rotateZ ?? lop?.rotateZ ?? 0;
1046
+ const tz = target?.translateZ ?? lop?.translateZ ?? 0;
1047
+ const p = target?.perspective ?? lop?.perspective ?? 0;
1048
+ if (rx !== 0 || ry !== 0 || tz !== 0 || p > 0 && (rx !== 0 || ry !== 0 || tz !== 0 || rz !== 0)) return true;
1049
+ if (target?.rotateZ !== void 0 && target.rotateZ !== 0 || lop?.rotateZ !== void 0 && lop.rotateZ !== 0) return true;
1050
+ const tracks = lop?.animation?.tracks;
1051
+ if (tracks && tracks.length > 0) {
1052
+ for (const t of tracks) if (t.prop === "rotateX" || t.prop === "rotateY" || t.prop === "rotateZ" || t.prop === "translateZ" || t.prop === "perspective") return true;
1053
+ }
1054
+ return false;
1055
+ }
1056
+
1057
+ //#endregion
1058
+ //#region src/renderers/webgpu-renderer.ts
1059
+ const LAYOUT_PROPS = [
1060
+ "width",
1061
+ "height",
1062
+ "gap",
1063
+ "padding",
1064
+ "fontSize",
1065
+ "letterSpacing"
1066
+ ];
1067
+ const layoutMemo = /* @__PURE__ */ new WeakMap();
1068
+ function layoutKey(width, height, targetsById, mediaDims) {
1069
+ const parts = [width, height];
1070
+ for (const id in targetsById) {
1071
+ const t = targetsById[id];
1072
+ for (const k of LAYOUT_PROPS) if (t[k] !== void 0) parts.push(id, k, t[k]);
1073
+ }
1074
+ for (const [id, d] of mediaDims) parts.push(id, d.width, d.height);
1075
+ return JSON.stringify(parts);
1076
+ }
1077
+ const compositorHomographyWgsl = `
1078
+ struct HomographyUniforms {
1079
+ matrix_row0 : vec4<f32>,
1080
+ matrix_row1 : vec4<f32>,
1081
+ matrix_row2 : vec4<f32>,
1082
+ params : vec4<f32>, // params.x = opacity
1083
+ };
1084
+
1085
+ @group(0) @binding(0) var<uniform> u : HomographyUniforms;
1086
+ @group(1) @binding(0) var tex : texture_2d<f32>;
1087
+ @group(1) @binding(1) var samp : sampler;
1088
+
1089
+ struct VSOut {
1090
+ @builtin(position) pos : vec4<f32>,
1091
+ @location(0) uv : vec2<f32>,
1092
+ };
1093
+
1094
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
1095
+ var pos = array<vec2<f32>, 4>(
1096
+ vec2<f32>(-1.0, 1.0),
1097
+ vec2<f32>(1.0, 1.0),
1098
+ vec2<f32>(-1.0, -1.0),
1099
+ vec2<f32>(1.0, -1.0)
1100
+ );
1101
+ var uv = array<vec2<f32>, 4>(
1102
+ vec2<f32>(0.0, 0.0),
1103
+ vec2<f32>(1.0, 0.0),
1104
+ vec2<f32>(0.0, 1.0),
1105
+ vec2<f32>(1.0, 1.0)
1106
+ );
1107
+ return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
1108
+ }
1109
+
1110
+ @fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
1111
+ let x = in.uv.x;
1112
+ let y = in.uv.y;
1113
+
1114
+ let d = u.matrix_row2.x * x + u.matrix_row2.y * y + u.matrix_row2.z;
1115
+ if (d <= 1e-6) {
1116
+ return vec4<f32>(0.0);
1117
+ }
1118
+
1119
+ let src_u = (u.matrix_row0.x * x + u.matrix_row0.y * y + u.matrix_row0.z) / d;
1120
+ let src_v = (u.matrix_row1.x * x + u.matrix_row1.y * y + u.matrix_row1.z) / d;
1121
+
1122
+ if (src_u < 0.0 || src_u > 1.0 || src_v < 0.0 || src_v > 1.0) {
1123
+ return vec4<f32>(0.0);
1124
+ }
1125
+
1126
+ let color = textureSampleLevel(tex, samp, vec2<f32>(src_u, src_v), 0.0);
1127
+ return color * u.params.x;
1128
+ }
1129
+ `;
1130
+ const deviceHomographyCache = /* @__PURE__ */ new WeakMap();
1131
+ function getHomographyResources(device, format) {
1132
+ let res = deviceHomographyCache.get(device);
1133
+ if (!res) {
1134
+ const uniformLayout = device.createBindGroupLayout({ entries: [{
1135
+ binding: 0,
1136
+ visibility: GPUShaderStage.FRAGMENT,
1137
+ buffer: { type: "uniform" }
1138
+ }] });
1139
+ const textureLayout = device.createBindGroupLayout({ entries: [{
1140
+ binding: 0,
1141
+ visibility: GPUShaderStage.FRAGMENT,
1142
+ texture: { sampleType: "float" }
1143
+ }, {
1144
+ binding: 1,
1145
+ visibility: GPUShaderStage.FRAGMENT,
1146
+ sampler: { type: "filtering" }
1147
+ }] });
1148
+ const module = device.createShaderModule({
1149
+ label: "compositor_homography.wgsl",
1150
+ code: compositorHomographyWgsl
1151
+ });
1152
+ res = {
1153
+ pipeline: device.createRenderPipeline({
1154
+ label: "CompositorHomographyPipeline",
1155
+ layout: device.createPipelineLayout({ bindGroupLayouts: [uniformLayout, textureLayout] }),
1156
+ vertex: {
1157
+ module,
1158
+ entryPoint: "vs"
1159
+ },
1160
+ fragment: {
1161
+ module,
1162
+ entryPoint: "fs",
1163
+ targets: [{
1164
+ format,
1165
+ blend: {
1166
+ color: {
1167
+ srcFactor: "one",
1168
+ dstFactor: "one-minus-src-alpha",
1169
+ operation: "add"
1170
+ },
1171
+ alpha: {
1172
+ srcFactor: "one",
1173
+ dstFactor: "one-minus-src-alpha",
1174
+ operation: "add"
1175
+ }
1176
+ }
1177
+ }]
1178
+ },
1179
+ primitive: { topology: "triangle-strip" }
1180
+ }),
1181
+ uniformLayout,
1182
+ textureLayout
1183
+ };
1184
+ deviceHomographyCache.set(device, res);
1185
+ }
1186
+ return res;
1187
+ }
1188
+ const audioTrackLatentCache = /* @__PURE__ */ new Map();
1189
+ const deviceRenderer3DCache = /* @__PURE__ */ new WeakMap();
1190
+ /** One Renderer3D per device, target format and MSAA sample count. */
1191
+ function getRenderer3D(device, format, sampleCount) {
1192
+ let byConfig = deviceRenderer3DCache.get(device);
1193
+ if (!byConfig) {
1194
+ byConfig = /* @__PURE__ */ new Map();
1195
+ deviceRenderer3DCache.set(device, byConfig);
1196
+ }
1197
+ const key = `${format}|${sampleCount}`;
1198
+ let r3d = byConfig.get(key);
1199
+ if (!r3d) {
1200
+ r3d = new Renderer3D(device, format, sampleCount);
1201
+ byConfig.set(key, r3d);
1202
+ }
1203
+ return r3d;
1204
+ }
1205
+ const deviceRelightPipelineCache = /* @__PURE__ */ new WeakMap();
1206
+ function getScreenSpaceRelightPipeline(device, format) {
1207
+ let relight = deviceRelightPipelineCache.get(device);
1208
+ if (!relight || relight.format !== format) {
1209
+ relight = new ScreenSpaceRelightPipeline(device, format);
1210
+ deviceRelightPipelineCache.set(device, relight);
1211
+ }
1212
+ return relight;
1213
+ }
1214
+ const deviceDeflickerPipelineCache = /* @__PURE__ */ new WeakMap();
1215
+ function getTemporalDeflickerPipeline(device, layerKey) {
1216
+ let map = deviceDeflickerPipelineCache.get(device);
1217
+ if (!map) {
1218
+ map = /* @__PURE__ */ new Map();
1219
+ deviceDeflickerPipelineCache.set(device, map);
1220
+ }
1221
+ let pipe = map.get(layerKey);
1222
+ if (!pipe) {
1223
+ pipe = new TemporalDeflickerPipeline(device);
1224
+ map.set(layerKey, pipe);
1225
+ }
1226
+ return pipe;
1227
+ }
1228
+ function resolveVal(val, fallback, frame, fps) {
1229
+ if (val === void 0 || val === null) return fallback;
1230
+ if (typeof val === "number") return Number.isFinite(val) ? val : fallback;
1231
+ if (typeof val === "object" && val !== null) {
1232
+ if ("get" in val && typeof val.get === "function") try {
1233
+ const res = val.get({
1234
+ frame,
1235
+ fps,
1236
+ time: fps > 0 ? frame / fps : 0,
1237
+ duration: 0,
1238
+ durationMs: 0,
1239
+ progress: 0,
1240
+ deltaTime: fps > 0 ? 1 / fps : 0
1241
+ });
1242
+ const n$1 = Number(res);
1243
+ if (Number.isFinite(n$1)) return n$1;
1244
+ } catch {}
1245
+ if ("value" in val) {
1246
+ const n$1 = Number(val.value);
1247
+ if (Number.isFinite(n$1)) return n$1;
1248
+ }
1249
+ }
1250
+ if (typeof val === "function") try {
1251
+ const n$1 = Number(val({
1252
+ frame,
1253
+ fps,
1254
+ time: fps > 0 ? frame / fps : 0
1255
+ }));
1256
+ if (Number.isFinite(n$1)) return n$1;
1257
+ } catch {}
1258
+ const n = Number(val);
1259
+ return Number.isFinite(n) ? n : fallback;
1260
+ }
1261
+ function synthesizeRelightLights(relightOpts, sceneLights, bounds, shiftedFrame, fps) {
1262
+ if (sceneLights.length > 0 || relightOpts.lightType === void 0 && relightOpts.intensity === void 0 && relightOpts.lightPosX === void 0) return sceneLights;
1263
+ const lightTypeStr = String(relightOpts.lightType ?? "point").toLowerCase();
1264
+ const lightKind = lightTypeStr === "directional" ? "directional" : lightTypeStr === "spot" ? "spot" : "point";
1265
+ const intensity = resolveVal(relightOpts.intensity ?? 1, 1, shiftedFrame, fps);
1266
+ const normX = resolveVal(relightOpts.lightPosX, .5, shiftedFrame, fps);
1267
+ const normY = resolveVal(relightOpts.lightPosY, .5, shiftedFrame, fps);
1268
+ const normZ = resolveVal(relightOpts.lightPosZ, .25, shiftedFrame, fps);
1269
+ const normRadius = resolveVal(relightOpts.lightRadius, .75, shiftedFrame, fps);
1270
+ const lightX = bounds.x + normX * bounds.width;
1271
+ const lightY = bounds.y + normY * bounds.height;
1272
+ const lightZ = normZ * Math.max(bounds.width, bounds.height);
1273
+ const lightRadius = normRadius * Math.max(bounds.width, bounds.height);
1274
+ const synthLight = {
1275
+ lightType: lightKind,
1276
+ color: relightOpts.lightColor ?? "#ffffff",
1277
+ intensity,
1278
+ x: lightX,
1279
+ y: lightY,
1280
+ z: lightZ,
1281
+ radius: lightRadius,
1282
+ decay: 1
1283
+ };
1284
+ const ambientInt = resolveVal(relightOpts.ambientIntensity, .15, shiftedFrame, fps);
1285
+ return [{
1286
+ lightType: "ambient",
1287
+ color: relightOpts.ambientColor ?? "#ffffff",
1288
+ intensity: ambientInt
1289
+ }, synthLight];
1290
+ }
1291
+ function drawProjectedQuad(ctx, encoder, destView, sourceTex, quad, destWidth, destHeight, opacity) {
1292
+ if (Math.abs(Transform3DMath.computeSignedArea(quad)) < 1e-4) return;
1293
+ const minX = Math.max(0, Math.min(destWidth, Math.floor(Math.min(quad.topLeft.x, quad.topRight.x, quad.bottomLeft.x, quad.bottomRight.x))));
1294
+ const minY = Math.max(0, Math.min(destHeight, Math.floor(Math.min(quad.topLeft.y, quad.topRight.y, quad.bottomLeft.y, quad.bottomRight.y))));
1295
+ const maxX = Math.max(0, Math.min(destWidth, Math.ceil(Math.max(quad.topLeft.x, quad.topRight.x, quad.bottomLeft.x, quad.bottomRight.x))));
1296
+ const maxY = Math.max(0, Math.min(destHeight, Math.ceil(Math.max(quad.topLeft.y, quad.topRight.y, quad.bottomLeft.y, quad.bottomRight.y))));
1297
+ const scissorW = maxX - minX;
1298
+ const scissorH = maxY - minY;
1299
+ if (scissorW <= 0 || scissorH <= 0) return;
1300
+ const h = solveHomography([
1301
+ {
1302
+ x: quad.topLeft.x / destWidth,
1303
+ y: quad.topLeft.y / destHeight
1304
+ },
1305
+ {
1306
+ x: quad.topRight.x / destWidth,
1307
+ y: quad.topRight.y / destHeight
1308
+ },
1309
+ {
1310
+ x: quad.bottomLeft.x / destWidth,
1311
+ y: quad.bottomLeft.y / destHeight
1312
+ },
1313
+ {
1314
+ x: quad.bottomRight.x / destWidth,
1315
+ y: quad.bottomRight.y / destHeight
1316
+ }
1317
+ ]);
1318
+ for (const val of h) if (!Number.isFinite(val)) return;
1319
+ const uniformData = new Float32Array(16);
1320
+ uniformData[0] = h[0];
1321
+ uniformData[1] = h[1];
1322
+ uniformData[2] = h[2];
1323
+ uniformData[3] = 0;
1324
+ uniformData[4] = h[3];
1325
+ uniformData[5] = h[4];
1326
+ uniformData[6] = h[5];
1327
+ uniformData[7] = 0;
1328
+ uniformData[8] = h[6];
1329
+ uniformData[9] = h[7];
1330
+ uniformData[10] = h[8];
1331
+ uniformData[11] = 0;
1332
+ uniformData[12] = opacity;
1333
+ uniformData[13] = 0;
1334
+ uniformData[14] = 0;
1335
+ uniformData[15] = 0;
1336
+ const { pipeline, uniformLayout, textureLayout } = getHomographyResources(ctx.device, ctx.renderer.format);
1337
+ const sampler = ctx.renderer.samplerCache.getSampler(ctx.device);
1338
+ const uniformBuffer = ctx.renderer.getTemporaryBuffer(uniformData);
1339
+ const uniformBindGroup = ctx.device.createBindGroup({
1340
+ layout: uniformLayout,
1341
+ entries: [{
1342
+ binding: 0,
1343
+ resource: { buffer: uniformBuffer }
1344
+ }]
1345
+ });
1346
+ const textureBindGroup = ctx.renderer.bindGroupCache.getBindGroup(ctx.device, textureLayout, sourceTex, sampler);
1347
+ const renderPass = encoder.beginRenderPass({ colorAttachments: [{
1348
+ view: destView,
1349
+ loadOp: "load",
1350
+ storeOp: "store"
1351
+ }] });
1352
+ renderPass.setPipeline(pipeline);
1353
+ renderPass.setScissorRect(minX, minY, scissorW, scissorH);
1354
+ renderPass.setBindGroup(0, uniformBindGroup);
1355
+ renderPass.setBindGroup(1, textureBindGroup);
1356
+ renderPass.draw(4);
1357
+ renderPass.end();
1358
+ }
1359
+ function computeContainerScissor(matrix, width, height) {
1360
+ const corners = [
1361
+ matrix.transformPoint(new DOMPoint(0, 0)),
1362
+ matrix.transformPoint(new DOMPoint(width, 0)),
1363
+ matrix.transformPoint(new DOMPoint(0, height)),
1364
+ matrix.transformPoint(new DOMPoint(width, height))
1365
+ ];
1366
+ const xs = corners.map((p) => p.x);
1367
+ const ys = corners.map((p) => p.y);
1368
+ const minX = Math.min(...xs);
1369
+ const minY = Math.min(...ys);
1370
+ const maxX = Math.max(...xs);
1371
+ const maxY = Math.max(...ys);
1372
+ return {
1373
+ x: Math.round(minX),
1374
+ y: Math.round(minY),
1375
+ width: Math.max(0, Math.round(maxX - minX)),
1376
+ height: Math.max(0, Math.round(maxY - minY))
1377
+ };
1378
+ }
1379
+ const CompositorWebGPURenderer = async (args) => {
1380
+ const { ctx, pass, targetView, targetTexture, targetWidth, targetHeight, encoder, props, drawChild } = args;
1381
+ const { virtualMedia, fps, frame } = props;
1382
+ const op = virtualMedia.operation;
1383
+ if (!op) return;
1384
+ pass.end();
1385
+ if (op.op === "CompositorLayer") {
1386
+ console.warn("CompositorWebGPURenderer called directly with legacy CompositorLayer operation. This is a bug.");
1387
+ args.pass = ctx.renderer.beginFrame(encoder, targetView, {
1388
+ r: 0,
1389
+ g: 0,
1390
+ b: 0,
1391
+ a: 0
1392
+ }, targetWidth, targetHeight, "load");
1393
+ return;
1394
+ }
1395
+ if (op.op === "Compositor") {
1396
+ const vop = op;
1397
+ const sampleCount3d = vop.antialias3d === false ? 1 : 4;
1398
+ const containerWidth = props.containerWidth ?? ctx.surface.width;
1399
+ const containerHeight = props.containerHeight ?? ctx.surface.height;
1400
+ const nativeWidth = vop.width ?? containerWidth;
1401
+ const nativeHeight = vop.height ?? containerHeight;
1402
+ const opacity = typeof vop.opacity === "number" ? vop.opacity : 1;
1403
+ const isVideoMode = props.isVideoMode ?? (vop.mode === "Video" || vop.dataType === "Video" || virtualMedia?.metadata?.durationMs != null);
1404
+ const inheritedSeekOffset = props.inheritedSeekOffset ?? 0;
1405
+ const shiftedFrame = Math.max(0, frame != null && fps != null ? frame + Math.round(inheritedSeekOffset * fps) : frame ?? 0);
1406
+ const compTex = ctx.renderer.getTemporaryTexture(nativeWidth, nativeHeight, [...props.excludeTextures || [], targetTexture]);
1407
+ const compView = compTex.createView();
1408
+ const composeDuration = virtualMedia?.metadata?.durationMs ?? DEFAULT_DURATION_MS;
1409
+ const durationSec = composeDuration / 1e3;
1410
+ const { tl, targetsById } = compileTimeline(props.renderId, virtualMedia, {
1411
+ fps: fps ?? 24,
1412
+ durationSec
1413
+ });
1414
+ tl.seek(shiftedFrame / (fps ?? 24));
1415
+ getRenderer3D(ctx.device, ctx.renderer.format ?? "rgba8unorm", sampleCount3d).resetPools();
1416
+ getScreenSpaceRelightPipeline(ctx.device, ctx.renderer.format ?? "rgba8unorm").resetPools();
1417
+ const clearColor = parseColor(vop.backgroundColor);
1418
+ ctx.renderer.beginFrame(encoder, compView, clearColor, nativeWidth, nativeHeight, "clear").end();
1419
+ let currentCompTex = compTex;
1420
+ ctx.renderer.pushScissor({
1421
+ x: 0,
1422
+ y: 0,
1423
+ width: nativeWidth,
1424
+ height: nativeHeight
1425
+ });
1426
+ ctx.renderer.pushIdentity();
1427
+ await initLayout();
1428
+ const mediaDims = /* @__PURE__ */ new Map();
1429
+ for (const vm of collectNodeOps(virtualMedia.children)) {
1430
+ const lop = vm.operation;
1431
+ if (lop.kind === "media") {
1432
+ const meta = getActiveMediaMetadata(vm.children?.[0]);
1433
+ if (meta?.width && meta?.height) mediaDims.set(lop.id, {
1434
+ width: meta.width,
1435
+ height: meta.height
1436
+ });
1437
+ else if (lop.dataType === "Caption" || (vm.children?.[0]?.operation)?.dataType === "Caption" || (vm.children?.[0]?.operation)?.srtText !== void 0) {
1438
+ const lopWidth = targetsById[lop.id]?.width ?? lop.width;
1439
+ const autoWidth = typeof lopWidth === "number" ? lopWidth : Math.round(nativeWidth * .8);
1440
+ const lopHeight = targetsById[lop.id]?.height ?? lop.height;
1441
+ let autoHeight = typeof lopHeight === "number" ? lopHeight : 160;
1442
+ const contentVM = vm.children?.[0];
1443
+ const src = resolveMediaSourceUrl(contentVM) ?? (contentVM?.operation)?.url ?? (contentVM?.operation)?.src ?? (contentVM?.operation)?.srtText ?? lop.src ?? lop.url;
1444
+ if (typeof lopHeight !== "number" && src) try {
1445
+ const captions = await srtLoader.load(src);
1446
+ if (captions && captions.length > 0) autoHeight = measureMaxCaptionHeight(captions, lop, autoWidth);
1447
+ } catch {}
1448
+ mediaDims.set(lop.id, {
1449
+ width: autoWidth,
1450
+ height: autoHeight
1451
+ });
1452
+ }
1453
+ }
1454
+ }
1455
+ const frameResolveNode = (vm) => {
1456
+ const lop = vm?.operation ?? {};
1457
+ const target = targetsById[lop.id];
1458
+ const pick = (k) => target && target[k] !== void 0 ? target[k] : lop[k];
1459
+ const node = {
1460
+ ...lop,
1461
+ width: pick("width"),
1462
+ height: pick("height"),
1463
+ gap: pick("gap"),
1464
+ padding: pick("padding"),
1465
+ fontSize: pick("fontSize"),
1466
+ letterSpacing: pick("letterSpacing")
1467
+ };
1468
+ node.children = (vm?.children ?? []).map(frameResolveNode);
1469
+ if (!node.children.length) node.children = void 0;
1470
+ return node;
1471
+ };
1472
+ const layoutRoots = () => (virtualMedia.children ?? []).map(frameResolveNode).filter((node) => node?.kind !== "camera" && node?.kind !== "light");
1473
+ const memoTree = virtualMedia.children;
1474
+ const memoKey = layoutKey(nativeWidth, nativeHeight, targetsById, mediaDims);
1475
+ const memo = memoTree ? layoutMemo.get(memoTree) : void 0;
1476
+ const { rects } = memo?.key === memoKey ? memo : await computeLayout({
1477
+ layout: layoutRoots(),
1478
+ viewport: {
1479
+ width: nativeWidth,
1480
+ height: nativeHeight
1481
+ },
1482
+ measure: (node, constraintWidth) => {
1483
+ if (node.kind === "text") {
1484
+ const textStyle = {
1485
+ fontSize: node.fontSize ?? 48,
1486
+ fontWeight: node.fontWeight,
1487
+ fontFamily: node.fontFamily,
1488
+ letterSpacing: node.letterSpacing,
1489
+ lineHeight: node.lineHeight,
1490
+ padding: node.padding
1491
+ };
1492
+ if (constraintWidth !== void 0) {
1493
+ const isAuto = typeof node.width !== "number";
1494
+ const wrapped$1 = measureText(node.text ?? "", {
1495
+ ...textStyle,
1496
+ width: isAuto ? void 0 : constraintWidth,
1497
+ keepNaturalWidth: true
1498
+ });
1499
+ return {
1500
+ width: isAuto ? Math.ceil(wrapped$1.width) : Math.min(constraintWidth, Math.ceil(wrapped$1.width)),
1501
+ height: Math.ceil(wrapped$1.height)
1502
+ };
1503
+ }
1504
+ const natural = measureText(node.text ?? "", textStyle);
1505
+ if (typeof node.width !== "number") return {
1506
+ width: Math.ceil(natural.width),
1507
+ height: Math.ceil(natural.height)
1508
+ };
1509
+ const wrapped = measureText(node.text ?? "", {
1510
+ ...textStyle,
1511
+ width: node.width
1512
+ });
1513
+ return {
1514
+ width: Math.ceil(wrapped.width),
1515
+ height: Math.ceil(wrapped.height)
1516
+ };
1517
+ }
1518
+ if (node.kind === "media") return mediaDims.get(node.id) ?? null;
1519
+ return null;
1520
+ }
1521
+ });
1522
+ if (memoTree && memo?.key !== memoKey) layoutMemo.set(memoTree, {
1523
+ key: memoKey,
1524
+ rects
1525
+ });
1526
+ const allNodeOps = collectNodeOps(virtualMedia.children ?? []);
1527
+ const cameraVMs = allNodeOps.filter((vm) => vm.operation?.kind === "camera");
1528
+ const cameraVM = isVideoMode ? cameraVMs.filter((vm) => {
1529
+ const op$1 = vm.operation;
1530
+ const start = op$1.startFrame ?? 0;
1531
+ const dur = op$1.durationFrames;
1532
+ return shiftedFrame >= start && (dur === void 0 || shiftedFrame < start + dur);
1533
+ }).sort((a, b) => (b.operation.startFrame ?? 0) - (a.operation.startFrame ?? 0))[0] : cameraVMs[0];
1534
+ const lightVMs = allNodeOps.filter((vm) => vm.operation?.kind === "light");
1535
+ const has3DLayer = allNodeOps.some((vm) => {
1536
+ const lop = vm.operation;
1537
+ const target = lop.id ? targetsById[lop.id] : void 0;
1538
+ return lop.kind !== "camera" && lop.kind !== "light" && (Boolean(target?.is3D ?? lop.is3D) || (target?.z ?? lop.z ?? 0) !== 0);
1539
+ });
1540
+ const is3DScene = Boolean(cameraVM || has3DLayer || lightVMs.length > 0);
1541
+ let camera = null;
1542
+ let dofEnabled = false;
1543
+ const sceneLights = [];
1544
+ if (is3DScene) {
1545
+ const camOp = cameraVM?.operation;
1546
+ const camStub = camOp?.id ? targetsById[camOp.id] : void 0;
1547
+ const fov = camStub?.cameraFov ?? camOp?.lens?.fov ?? 50;
1548
+ camera = Camera3D.createDefaultCamera(nativeWidth, nativeHeight, fov);
1549
+ camera.near = camOp?.lens?.near ?? 1;
1550
+ camera.far = camOp?.lens?.far ?? 5e4;
1551
+ camera.zoom = camStub?.cameraZoom ?? camOp?.lens?.zoom ?? 1;
1552
+ const defaultEye = camera.eye;
1553
+ const defaultTarget = camera.target;
1554
+ const eyeX = camStub?.cameraX ?? camStub?.x ?? camOp?.x ?? defaultEye[0];
1555
+ const eyeY = camStub?.cameraY ?? camStub?.y ?? camOp?.y ?? defaultEye[1];
1556
+ const eyeZ = camStub?.cameraZ ?? camStub?.z ?? camOp?.z ?? defaultEye[2];
1557
+ camera.eye = [
1558
+ eyeX,
1559
+ eyeY,
1560
+ eyeZ
1561
+ ];
1562
+ if (camStub?.targetX !== void 0 || camStub?.targetY !== void 0 || camStub?.targetZ !== void 0 || camOp?.targetX !== void 0 || camOp?.targetY !== void 0 || camOp?.targetZ !== void 0) camera.target = [
1563
+ camStub?.targetX ?? camOp?.targetX ?? defaultTarget[0],
1564
+ camStub?.targetY ?? camOp?.targetY ?? defaultTarget[1],
1565
+ camStub?.targetZ ?? camOp?.targetZ ?? defaultTarget[2]
1566
+ ];
1567
+ else camera.target = [
1568
+ eyeX,
1569
+ eyeY,
1570
+ 0
1571
+ ];
1572
+ const pitch = camStub?.cameraPitch ?? camOp?.pitch ?? 0;
1573
+ const yaw = camStub?.cameraYaw ?? camOp?.yaw ?? 0;
1574
+ const roll = camStub?.cameraRoll ?? camOp?.roll ?? 0;
1575
+ if (pitch !== 0 || yaw !== 0 || roll !== 0) camera = Camera3D.applyOrientation(camera, pitch, yaw, roll);
1576
+ const orbitAzimuth = camStub?.orbitAzimuth;
1577
+ const orbitElevation = camStub?.orbitElevation;
1578
+ const orbitRadius = camStub?.orbitRadius;
1579
+ if (orbitAzimuth !== void 0 || orbitElevation !== void 0 || orbitRadius !== void 0) {
1580
+ const radius = orbitRadius ?? Vector3Math.distance(camera.eye, camera.target);
1581
+ camera.eye = Camera3D.orbit(camera.target, radius, orbitAzimuth ?? 0, orbitElevation ?? 0);
1582
+ }
1583
+ const shakeTrans = camStub?.shakeTranslation ?? (camOp?.shake?.enabled ? camOp?.shake?.translationAmplitude : 0) ?? 0;
1584
+ const shakeRot = camStub?.shakeRotation ?? (camOp?.shake?.enabled ? camOp?.shake?.rotationAmplitude : 0) ?? 0;
1585
+ if (shakeTrans > 0 || shakeRot > 0) {
1586
+ const t = shiftedFrame / (fps ?? 24);
1587
+ camera = Camera3D.applyCameraShake(camera, t, {
1588
+ translationAmplitude: shakeTrans,
1589
+ rotationAmplitude: shakeRot,
1590
+ frequency: camOp?.shake?.frequency ?? 1.5,
1591
+ octaves: camOp?.shake?.octaves ?? 3,
1592
+ seed: camOp?.shake?.seed ?? 42
1593
+ });
1594
+ }
1595
+ dofEnabled = Boolean(camOp?.dof?.enabled || camStub?.focusDistance !== void 0);
1596
+ if (dofEnabled) {
1597
+ camera.focusDistance = camStub?.focusDistance ?? camOp?.dof?.focusDistance ?? Vector3Math.distance(camera.eye, camera.target);
1598
+ camera.fStop = camStub?.fStop ?? camOp?.dof?.fStop ?? 2.8;
1599
+ camera.maxBlurRadius = camStub?.maxBlurRadius ?? camOp?.dof?.maxBlurRadius ?? 24;
1600
+ }
1601
+ for (const lvm of lightVMs) {
1602
+ const lop = lvm.operation;
1603
+ const targetId = lop.id || lop.inputHandleId;
1604
+ const target = targetId ? targetsById[targetId] : void 0;
1605
+ if (target?.hidden || lop.hidden) continue;
1606
+ if (isVideoMode) {
1607
+ const startFrame = lop.startFrame ?? 0;
1608
+ const durationFrames = lop.durationFrames;
1609
+ if (shiftedFrame < startFrame) continue;
1610
+ if (durationFrames !== void 0 && shiftedFrame >= startFrame + durationFrames) continue;
1611
+ }
1612
+ const lightType = target?.lightType ?? lop.lightType ?? "point";
1613
+ const color = parseColor(target?.color ?? lop.color ?? "#ffffff");
1614
+ const intensity = target?.intensity ?? lop.intensity ?? 1;
1615
+ const x = target?.x ?? lop.x ?? 0;
1616
+ const y = target?.y ?? lop.y ?? 0;
1617
+ const z = target?.z ?? lop.z ?? 0;
1618
+ const targetX = target?.targetX ?? lop.targetX ?? 0;
1619
+ const targetY = target?.targetY ?? lop.targetY ?? 0;
1620
+ const targetZ = target?.targetZ ?? lop.targetZ ?? 0;
1621
+ const radius = target?.radius ?? lop.radius ?? 1e3;
1622
+ const angle = target?.angle ?? lop.angle ?? 45;
1623
+ const penumbra = target?.penumbra ?? lop.penumbra ?? .2;
1624
+ const decay = target?.decay ?? lop.decay ?? 1;
1625
+ sceneLights.push({
1626
+ type: lightType,
1627
+ color,
1628
+ intensity,
1629
+ position: [
1630
+ x,
1631
+ y,
1632
+ z
1633
+ ],
1634
+ target: [
1635
+ targetX,
1636
+ targetY,
1637
+ targetZ
1638
+ ],
1639
+ radius,
1640
+ angle,
1641
+ penumbra,
1642
+ decay
1643
+ });
1644
+ }
1645
+ }
1646
+ const pending3DItems = [];
1647
+ const flush3DPass = (curTex, curView, curExcludes) => {
1648
+ if (pending3DItems.length === 0 || !camera) return {
1649
+ finalTex: curTex,
1650
+ finalView: curView
1651
+ };
1652
+ const r3d = getRenderer3D(ctx.device, ctx.renderer.format ?? "rgba8unorm", sampleCount3d);
1653
+ for (const item of pending3DItems) if (item.kind === "mesh" && item.opts.audioDeform) item.meshBuffers = r3d.deformMesh(encoder, item.mesh, item.opts.audioDeform);
1654
+ const itemTextures = pending3DItems.map((q) => q.kind === "quad" ? q.texture : q.texture).filter((t) => Boolean(t));
1655
+ const scene3dTex = ctx.renderer.getTemporaryTexture(nativeWidth, nativeHeight, [
1656
+ curTex,
1657
+ targetTexture,
1658
+ ...curExcludes,
1659
+ ...itemTextures
1660
+ ]);
1661
+ const scene3dView = scene3dTex.createView();
1662
+ let linearDepthTex;
1663
+ let linearDepthView;
1664
+ if (dofEnabled) {
1665
+ linearDepthTex = r3d.getOrCreateLinearDepthTexture(nativeWidth, nativeHeight);
1666
+ linearDepthView = linearDepthTex.createView();
1667
+ }
1668
+ const { pass: pass3d, cameraBindGroup, cameraBuffer, lightsBindGroup } = r3d.beginPass(encoder, scene3dView, nativeWidth, nativeHeight, camera, "clear", {
1669
+ r: 0,
1670
+ g: 0,
1671
+ b: 0,
1672
+ a: 0
1673
+ }, linearDepthView, sceneLights);
1674
+ for (const item of pending3DItems) if (item.kind === "quad") r3d.drawTextureQuad(pass3d, cameraBindGroup, lightsBindGroup, item.texture, item.opts, dofEnabled);
1675
+ else if (item.kind === "slug") r3d.drawSlugText(pass3d, cameraBindGroup, lightsBindGroup, item.batches, item.opts, item.stackBase, dofEnabled);
1676
+ else if (item.kind === "mesh") r3d.drawMesh3D(pass3d, cameraBindGroup, lightsBindGroup, item.meshBuffers ?? item.mesh, item.texture, item.opts, dofEnabled);
1677
+ pass3d.end();
1678
+ let final3dTex = scene3dTex;
1679
+ if (dofEnabled && linearDepthTex) {
1680
+ const dofResultTex = ctx.renderer.getTemporaryTexture(nativeWidth, nativeHeight, [
1681
+ curTex,
1682
+ targetTexture,
1683
+ scene3dTex,
1684
+ ...curExcludes
1685
+ ]);
1686
+ r3d.applyDepthOfField(encoder, scene3dTex, linearDepthTex, dofResultTex.createView(), cameraBuffer);
1687
+ final3dTex = dofResultTex;
1688
+ }
1689
+ const nextTex = ctx.renderer.composite(encoder, curTex, final3dTex, "normal", [targetTexture, ...curExcludes]);
1690
+ pending3DItems.length = 0;
1691
+ return {
1692
+ finalTex: nextTex,
1693
+ finalView: nextTex.createView()
1694
+ };
1695
+ };
1696
+ let nodeIndex = 0;
1697
+ const renderNodeTree = async (vms, initialTex, initialView, excludes, accumulatedOpacity = 1, parentMatrix = new DOMMatrix(), parentRect = null, targetW = nativeWidth, targetH = nativeHeight, parent3DMatrix = null, isRootScene = false) => {
1698
+ const sorted = [...vms].sort((a, b) => {
1699
+ return (a.operation?.zIndex ?? 0) - (b.operation?.zIndex ?? 0);
1700
+ });
1701
+ let activeTex = initialTex;
1702
+ let activeView = initialView;
1703
+ let activeExcludes = excludes;
1704
+ for (const child of sorted) {
1705
+ const lop = child.operation;
1706
+ if (lop.kind === "camera" || lop.kind === "light") continue;
1707
+ const targetId = lop.id || lop.inputHandleId;
1708
+ const target = targetId ? targetsById[targetId] : void 0;
1709
+ const isChild3D = Boolean(target?.is3D ?? lop.is3D) || Boolean(camera && ((target?.z ?? lop.z ?? 0) !== 0 || lop.is3D)) || Boolean(parent3DMatrix);
1710
+ if (isRootScene && !isChild3D && pending3DItems.length > 0) {
1711
+ const flushed = flush3DPass(activeTex, activeView, activeExcludes);
1712
+ if (flushed.finalTex !== activeTex) activeExcludes = [...activeExcludes, flushed.finalTex];
1713
+ activeTex = flushed.finalTex;
1714
+ activeView = flushed.finalView;
1715
+ }
1716
+ if (lop.op !== "CompositorLayer") {
1717
+ await drawChild(child, {
1718
+ containerWidth: targetW,
1719
+ containerHeight: targetH,
1720
+ renderId: `${props.renderId}-c${nodeIndex}-${lop.op}`,
1721
+ fps,
1722
+ isVideoMode,
1723
+ frame: shiftedFrame,
1724
+ excludeTextures: [targetTexture, ...activeExcludes]
1725
+ }, activeView, activeTex, targetW, targetH);
1726
+ continue;
1727
+ }
1728
+ nodeIndex += 1;
1729
+ const res = await renderLayerVM(child, nodeIndex, activeTex, activeView, activeExcludes, accumulatedOpacity, parentMatrix, parentRect, targetW, targetH, parent3DMatrix, isRootScene);
1730
+ if (res) {
1731
+ if (res.finalTex !== activeTex) activeExcludes = [...activeExcludes, res.finalTex];
1732
+ activeTex = res.finalTex;
1733
+ activeView = res.finalView;
1734
+ }
1735
+ }
1736
+ if (isRootScene && pending3DItems.length > 0) {
1737
+ const flushed = flush3DPass(activeTex, activeView, activeExcludes);
1738
+ if (flushed.finalTex !== activeTex) activeExcludes = [...activeExcludes, flushed.finalTex];
1739
+ activeTex = flushed.finalTex;
1740
+ activeView = flushed.finalView;
1741
+ }
1742
+ return {
1743
+ finalTex: activeTex,
1744
+ finalView: activeView
1745
+ };
1746
+ };
1747
+ const renderLayerVM = async (nodeVM, i, destTex, destView, baseExcludes, accumulatedOpacity, parentMatrix, parentRect, targetW = nativeWidth, targetH = nativeHeight, parent3DMatrix = null, isRootScene = false) => {
1748
+ const excludes = [...baseExcludes, ...pending3DItems.map((q) => q.texture).filter((t) => Boolean(t))];
1749
+ const lop = nodeVM.operation;
1750
+ if (lop.kind === "camera" || lop.kind === "light") return;
1751
+ const targetId = lop.id || lop.inputHandleId;
1752
+ const target = targetId ? targetsById[targetId] : void 0;
1753
+ if (target?.hidden || lop.hidden) return;
1754
+ const kind = lop.kind;
1755
+ const isContainer = kind === "flex" || kind === "block" || kind === "box";
1756
+ if (isVideoMode) {
1757
+ const startFrame = lop.startFrame ?? 0;
1758
+ const durationFrames = lop.durationFrames;
1759
+ if (shiftedFrame < startFrame) return;
1760
+ if (durationFrames !== void 0 && shiftedFrame >= startFrame + durationFrames) return;
1761
+ }
1762
+ if (kind === "model3d") {
1763
+ const r3d = getRenderer3D(ctx.device, ctx.renderer.format ?? "rgba8unorm", sampleCount3d);
1764
+ let modelSrc = lop.modelData ?? lop.src;
1765
+ if (!modelSrc && lop.text3dOptions) {
1766
+ const tOpts = lop.text3dOptions;
1767
+ const font = (tOpts.fontFamily ? SlugFontCache.getParsed(tOpts.fontFamily) : null) ?? SlugFontCache.getParsed("Inter") ?? SlugFontCache.getParsed("Unbounded") ?? SlugFontCache.getFirstParsed();
1768
+ if (font) {
1769
+ modelSrc = generateExtrudedTextGeometry({
1770
+ ...tOpts,
1771
+ font
1772
+ }).modelData;
1773
+ lop.modelData = modelSrc;
1774
+ }
1775
+ }
1776
+ if (!modelSrc) return;
1777
+ let model;
1778
+ try {
1779
+ model = typeof modelSrc === "object" && modelSrc !== null && "meshes" in modelSrc ? modelSrc : r3d.loadModel(modelSrc, lop.modelFormat ?? "auto", {
1780
+ center: lop.center,
1781
+ normalizeSize: lop.normalizeSize
1782
+ });
1783
+ } catch (err) {
1784
+ console.error("Failed to load 3D model:", err);
1785
+ return;
1786
+ }
1787
+ if (!model || model.meshes.length === 0) return;
1788
+ const posX = target?.x ?? lop.x ?? 0;
1789
+ const posY = target?.y ?? lop.y ?? 0;
1790
+ const posZ = target?.z ?? lop.z ?? 0;
1791
+ const rotX = target?.rotateX ?? lop.rotateX ?? 0;
1792
+ const rotY = target?.rotateY ?? lop.rotateY ?? 0;
1793
+ const rotZ = (target?.rotateZ ?? lop.rotateZ ?? 0) !== 0 ? target?.rotateZ ?? lop.rotateZ ?? 0 : target?.rotation ?? lop.rotation ?? 0;
1794
+ const sc = target?.scale ?? lop.scale ?? 1;
1795
+ const scX = (target?.scaleX ?? lop.scaleX ?? 1) * sc;
1796
+ const scY = (target?.scaleY ?? lop.scaleY ?? 1) * sc;
1797
+ const scZ = (target?.scaleZ ?? lop.scaleZ ?? 1) * sc;
1798
+ let localM = Matrix4Math.identity();
1799
+ localM = Matrix4Math.multiply(localM, Matrix4Math.translate(posX, posY, posZ));
1800
+ const rxRad = rotX * Math.PI / 180;
1801
+ const ryRad = rotY * Math.PI / 180;
1802
+ const rzRad = rotZ * Math.PI / 180;
1803
+ if (rzRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateZ(rzRad));
1804
+ if (ryRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateY(ryRad));
1805
+ if (rxRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateX(rxRad));
1806
+ localM = Matrix4Math.multiply(localM, Matrix4Math.scale(scX, scY, scZ));
1807
+ const finalModelMatrix = parent3DMatrix ? Matrix4Math.multiply(parent3DMatrix, localM) : localM;
1808
+ const colorParsed = target?.colorTint || target?.color || lop.colorTint || lop.color ? parseColor(target?.colorTint ?? target?.color ?? lop.colorTint ?? lop.color) : void 0;
1809
+ const colorTint = colorParsed ? [
1810
+ colorParsed.r,
1811
+ colorParsed.g,
1812
+ colorParsed.b,
1813
+ colorParsed.a
1814
+ ] : [
1815
+ 1,
1816
+ 1,
1817
+ 1,
1818
+ 1
1819
+ ];
1820
+ const modelOpacity = (target?.opacity ?? lop.opacity ?? 1) * accumulatedOpacity;
1821
+ let skinningMatrices;
1822
+ const animProgress = target?.animationProgress ?? lop.animationProgress ?? 0;
1823
+ const animTime = target?.animationTime ?? lop.animationTime ?? 0;
1824
+ const animSpeed = target?.animationSpeed ?? lop.animationSpeed ?? 1;
1825
+ const clip = (lop.animationName ? model.animations.find((a) => a.name === lop.animationName) : void 0) ?? model.animations[0];
1826
+ let clipSec = 0;
1827
+ if (clip && clip.duration > 0) {
1828
+ clipSec = animProgress > 0 ? animProgress * clip.duration : animTime + (shiftedFrame ?? 0) / (fps || 30) * animSpeed;
1829
+ if (lop.loop ?? true) clipSec %= clip.duration;
1830
+ }
1831
+ const sceneRoot = Matrix4Math.multiply(finalModelMatrix, model.rootTransform ?? Matrix4Math.identity());
1832
+ const posed = model.nodes ? poseNodes(model, clip, clipSec) : void 0;
1833
+ const skinCache = /* @__PURE__ */ new Map();
1834
+ const placeMesh = (mesh) => {
1835
+ if (!posed) return { matrix: sceneRoot };
1836
+ if (mesh.skin !== void 0) {
1837
+ let joints = skinCache.get(mesh.skin);
1838
+ if (!joints) {
1839
+ joints = jointMatrices(model, mesh.skin, posed);
1840
+ skinCache.set(mesh.skin, joints);
1841
+ }
1842
+ return {
1843
+ matrix: sceneRoot,
1844
+ joints
1845
+ };
1846
+ }
1847
+ if (mesh.node !== void 0) return { matrix: Matrix4Math.multiply(sceneRoot, posed[mesh.node]) };
1848
+ return { matrix: sceneRoot };
1849
+ };
1850
+ if (!posed && clip && clip.duration > 0) {
1851
+ const currentSec = clipSec;
1852
+ if (model.skeleton && model.skeleton.bones.length > 0) {
1853
+ const numBones = Math.min(128, model.skeleton.bones.length);
1854
+ skinningMatrices = new Float32Array(2048);
1855
+ for (let b = 0; b < 128; b++) {
1856
+ skinningMatrices[b * 16] = 1;
1857
+ skinningMatrices[b * 16 + 5] = 1;
1858
+ skinningMatrices[b * 16 + 10] = 1;
1859
+ skinningMatrices[b * 16 + 15] = 1;
1860
+ }
1861
+ for (const track of clip.tracks) {
1862
+ const boneIdx = model.skeleton.boneIndicesByName[track.nodeName];
1863
+ if (boneIdx !== void 0 && boneIdx < numBones) {
1864
+ const times = track.times;
1865
+ let sampleIdx = 0;
1866
+ for (let k = 0; k < times.length - 1; k++) if (currentSec >= times[k] && currentSec <= times[k + 1]) {
1867
+ sampleIdx = k;
1868
+ break;
1869
+ }
1870
+ const t0 = times[sampleIdx] ?? 0;
1871
+ const t1 = times[sampleIdx + 1] ?? t0;
1872
+ const alpha = t1 > t0 ? (currentSec - t0) / (t1 - t0) : 0;
1873
+ if (track.translations) {
1874
+ const x0 = track.translations[sampleIdx * 3] ?? 0;
1875
+ const y0 = track.translations[sampleIdx * 3 + 1] ?? 0;
1876
+ const z0 = track.translations[sampleIdx * 3 + 2] ?? 0;
1877
+ const x1 = track.translations[(sampleIdx + 1) * 3] ?? x0;
1878
+ const y1 = track.translations[(sampleIdx + 1) * 3 + 1] ?? y0;
1879
+ const z1 = track.translations[(sampleIdx + 1) * 3 + 2] ?? z0;
1880
+ const curX = x0 + (x1 - x0) * alpha;
1881
+ const curY = y0 + (y1 - y0) * alpha;
1882
+ const curZ = z0 + (z1 - z0) * alpha;
1883
+ const boneMat = Matrix4Math.translate(curX, curY, curZ);
1884
+ const bone = model.skeleton.bones[boneIdx];
1885
+ const skinMat = bone ? Matrix4Math.multiply(boneMat, bone.inverseBindMatrix) : boneMat;
1886
+ skinningMatrices.set(skinMat, boneIdx * 16);
1887
+ }
1888
+ }
1889
+ }
1890
+ }
1891
+ }
1892
+ const isBlend = (m) => m.materialName ? model.materials[m.materialName]?.alphaMode === "blend" : false;
1893
+ const drawOrder = [...model.meshes].sort((a, b) => Number(isBlend(a)) - Number(isBlend(b)));
1894
+ for (const mesh of drawOrder) {
1895
+ const mat = mesh.materialName ? model.materials[mesh.materialName] : void 0;
1896
+ const placed = placeMesh(mesh);
1897
+ const meshSkin = placed.joints ?? skinningMatrices;
1898
+ let meshColorTint;
1899
+ if (colorParsed) if (mat?.diffuseColor) meshColorTint = [
1900
+ mat.diffuseColor[0] * colorTint[0],
1901
+ mat.diffuseColor[1] * colorTint[1],
1902
+ mat.diffuseColor[2] * colorTint[2],
1903
+ (mat.diffuseColor[3] ?? 1) * colorTint[3]
1904
+ ];
1905
+ else meshColorTint = colorTint;
1906
+ else if (mat?.diffuseColor) meshColorTint = mat.diffuseColor;
1907
+ else meshColorTint = [
1908
+ .8,
1909
+ .8,
1910
+ .85,
1911
+ 1
1912
+ ];
1913
+ const audioDeformConfig = target?.audioDeform ?? lop.audioDeform;
1914
+ let meshAudioDeform;
1915
+ if (audioDeformConfig) {
1916
+ const currentSec = (shiftedFrame ?? 0) / (fps || 30);
1917
+ const timeMs = currentSec * 1e3;
1918
+ let latentBuffer = audioDeformConfig.latentBuffer;
1919
+ if (!latentBuffer && audioDeformConfig.audioBuffer) latentBuffer = audioDeformConfig.audioBuffer;
1920
+ if (!latentBuffer) {
1921
+ const directAudio = audioDeformConfig.audioData;
1922
+ if (directAudio) {
1923
+ const cacheKey = (lop.id ?? "audio-mesh") + "-direct-audio";
1924
+ let trackCache = audioTrackLatentCache.get(cacheKey);
1925
+ if (!trackCache) {
1926
+ const chs = Array.isArray(directAudio) ? directAudio : directAudio.channels;
1927
+ const sRate = Array.isArray(directAudio) ? 48e3 : directAudio.sampleRate;
1928
+ const totalF = Math.max(10, Math.ceil((chs[0]?.length ?? 0) / sRate * (fps || 30)) + 10);
1929
+ trackCache = new AudioLatentTrackCache(chs, sRate, fps || 30, totalF);
1930
+ audioTrackLatentCache.set(cacheKey, trackCache);
1931
+ }
1932
+ latentBuffer = trackCache.getGpuBuffer(ctx.device, shiftedFrame ?? 0);
1933
+ }
1934
+ }
1935
+ if (!latentBuffer && audioDeformConfig.audioTrackId) {
1936
+ const trackId = audioDeformConfig.audioTrackId;
1937
+ let foundAudioUrl;
1938
+ const searchVms = (list) => {
1939
+ for (const v of list) {
1940
+ const op$1 = v.operation;
1941
+ if (op$1) {
1942
+ if (op$1.id === trackId || op$1.inputHandleId === trackId) {
1943
+ foundAudioUrl = op$1.inputHandleId || op$1.src || op$1.url;
1944
+ return;
1945
+ }
1946
+ if (v.children) searchVms(v.children);
1947
+ }
1948
+ }
1949
+ };
1950
+ if (virtualMedia.children) searchVms(virtualMedia.children);
1951
+ if (!foundAudioUrl && (trackId.endsWith(".mp3") || trackId.endsWith(".wav") || trackId.endsWith(".ogg"))) foundAudioUrl = trackId;
1952
+ if (foundAudioUrl) try {
1953
+ let trackCache = audioTrackLatentCache.get(foundAudioUrl);
1954
+ if (!trackCache) {
1955
+ const decoded = await decodeAudioSource(foundAudioUrl);
1956
+ if (decoded && decoded.channels.length > 0) {
1957
+ const sRate = decoded.sampleRate || 48e3;
1958
+ const ch0Len = decoded.channels[0]?.length ?? 0;
1959
+ const durationSec$1 = decoded.durationSec ?? ch0Len / sRate;
1960
+ const totalF = Math.max(10, Math.ceil(durationSec$1 * (fps || 30)) + 10);
1961
+ trackCache = new AudioLatentTrackCache(decoded.channels, sRate, fps || 30, totalF);
1962
+ audioTrackLatentCache.set(foundAudioUrl, trackCache);
1963
+ }
1964
+ }
1965
+ if (trackCache) latentBuffer = trackCache.getGpuBuffer(ctx.device, shiftedFrame ?? 0);
1966
+ } catch (err) {
1967
+ console.warn("[webgpu-renderer] Failed to decode audio for deformWithAudio:", err);
1968
+ }
1969
+ }
1970
+ if (latentBuffer) {
1971
+ let amplitudeMultiplier = audioDeformConfig.amplitudeMultiplier ?? 1;
1972
+ if (typeof amplitudeMultiplier !== "number" && typeof amplitudeMultiplier?.get === "function") {
1973
+ const frameCtx = {
1974
+ frame: shiftedFrame ?? 0,
1975
+ fps: fps || 30,
1976
+ time: currentSec,
1977
+ duration: 0,
1978
+ durationMs: 0,
1979
+ progress: 0,
1980
+ deltaTime: 1 / (fps || 30)
1981
+ };
1982
+ amplitudeMultiplier = Number(amplitudeMultiplier.get(frameCtx));
1983
+ } else if (typeof amplitudeMultiplier !== "number" && typeof amplitudeMultiplier?.value === "number") amplitudeMultiplier = amplitudeMultiplier.value;
1984
+ meshAudioDeform = {
1985
+ config: {
1986
+ ...audioDeformConfig,
1987
+ amplitudeMultiplier: Number.isFinite(amplitudeMultiplier) ? amplitudeMultiplier : 1
1988
+ },
1989
+ latentBuffer,
1990
+ timeMs,
1991
+ sampleRate: 48e3
1992
+ };
1993
+ }
1994
+ }
1995
+ const meshOpts = {
1996
+ modelMatrix: placed.matrix,
1997
+ opacity: modelOpacity * (mat?.opacity ?? 1),
1998
+ colorTint: meshColorTint,
1999
+ twoSided: target?.twoSided ?? lop.twoSided ?? mat?.twoSided ?? true,
2000
+ wireframe: Boolean(target?.wireframe ?? lop.wireframe),
2001
+ isSkinned: Boolean(meshSkin || mesh.jointIndices && mesh.jointWeights),
2002
+ skinningMatrices: meshSkin,
2003
+ material: target?.material ?? lop.material ?? mat?.shading ?? "lit",
2004
+ shadeColor: mat?.shadeColor,
2005
+ alphaCutoff: mat?.alphaCutoff,
2006
+ textureWrap: mat?.textureWrap,
2007
+ shininess: target?.shininess ?? lop.shininess ?? mat?.shininess ?? 32,
2008
+ roughness: target?.roughness ?? lop.roughness ?? mat?.roughness,
2009
+ specularIntensity: target?.specularIntensity ?? lop.specularIntensity ?? .5,
2010
+ ambientIntensity: target?.ambientIntensity ?? lop.ambientIntensity ?? 1,
2011
+ metallic: target?.metallic ?? lop.metallic ?? mat?.metallic,
2012
+ audioDeform: meshAudioDeform
2013
+ };
2014
+ pending3DItems.push({
2015
+ kind: "mesh",
2016
+ mesh,
2017
+ texture: mat ? await r3d.materialTexture(mat) : void 0,
2018
+ opts: meshOpts
2019
+ });
2020
+ }
2021
+ return {
2022
+ finalTex: destTex,
2023
+ finalView: destView
2024
+ };
2025
+ }
2026
+ const rect = rects[lop.id];
2027
+ if (!rect) {
2028
+ if (isContainer) return await renderNodeTree(nodeVM.children ?? [], destTex, destView, excludes, accumulatedOpacity, parentMatrix, parentRect, targetW, targetH, parent3DMatrix, isRootScene);
2029
+ return;
2030
+ }
2031
+ const baseX = lop.x ?? 0;
2032
+ const baseY = lop.y ?? 0;
2033
+ const sampledX = target?.x ?? baseX;
2034
+ const sampledY = target?.y ?? baseY;
2035
+ let localX = 0;
2036
+ let localY = 0;
2037
+ if (parentRect) {
2038
+ const localLayoutX = rect.x - parentRect.x;
2039
+ const localLayoutY = rect.y - parentRect.y;
2040
+ localX = localLayoutX + (sampledX - baseX);
2041
+ localY = localLayoutY + (sampledY - baseY);
2042
+ } else {
2043
+ localX = rect.x + (sampledX - baseX);
2044
+ localY = rect.y + (sampledY - baseY);
2045
+ }
2046
+ const scale$1 = target?.scale ?? lop.scale ?? 1;
2047
+ const rotation = target?.rotation ?? lop.rotation ?? 0;
2048
+ const layerOpacity = (target?.opacity ?? lop.opacity ?? 1) * accumulatedOpacity;
2049
+ const rawAnimators = target?.animators ?? lop.animators;
2050
+ const isPathText = kind === "text" && Boolean(lop.pathOptions || target?.pathOptions);
2051
+ const hasKineticAnimators = kind === "text" && Array.isArray(rawAnimators) && rawAnimators.length > 0;
2052
+ const hasFormation = kind === "text" && Array.isArray(rawAnimators) && rawAnimators.some((a) => {
2053
+ const anim = a;
2054
+ return anim?.props?.formation !== void 0 || anim?.formation !== void 0;
2055
+ });
2056
+ let padX = 0;
2057
+ let padY = 0;
2058
+ if (hasFormation) {
2059
+ let formationRadius = 0;
2060
+ let formationPitch = 0;
2061
+ for (const a of rawAnimators) {
2062
+ const anim = a;
2063
+ const f = anim?.props?.formation ?? anim?.formation;
2064
+ if (f) {
2065
+ if (typeof f.radius === "number") formationRadius = Math.max(formationRadius, f.radius);
2066
+ if (typeof f.pitch === "number") formationPitch = Math.max(formationPitch, f.pitch);
2067
+ }
2068
+ }
2069
+ padX = Math.max(160, Math.ceil(formationRadius + 100));
2070
+ padY = Math.max(100, Math.ceil(formationRadius + formationPitch * 15 + 100));
2071
+ } else if (!isPathText && hasKineticAnimators) {
2072
+ padX = Math.max(80, Math.ceil(rect.width * .2));
2073
+ padY = Math.max(50, Math.ceil(rect.height * .5));
2074
+ }
2075
+ const layerW = isPathText ? targetW : rect.width + padX * 2;
2076
+ const layerH = isPathText ? targetH : rect.height + padY * 2;
2077
+ if (!isPathText && hasKineticAnimators) {
2078
+ localX -= padX;
2079
+ localY -= padY;
2080
+ }
2081
+ const anchorX = lop.anchorX ?? .5;
2082
+ const anchorY = lop.anchorY ?? .5;
2083
+ const localMatrix = toDOMMatrix(buildLayerMatrix({
2084
+ x: localX,
2085
+ y: localY,
2086
+ width: layerW,
2087
+ height: layerH,
2088
+ rotation,
2089
+ scale: scale$1,
2090
+ anchorX: isPathText ? anchorX * rect.width / layerW : anchorX,
2091
+ anchorY: isPathText ? anchorY * rect.height / layerH : anchorY
2092
+ }));
2093
+ const layerMatrix = parentMatrix.multiply(localMatrix);
2094
+ const blendMode = lop.blendMode || "normal";
2095
+ let activeTex = destTex;
2096
+ let activeView = destView;
2097
+ const is3D = has3DTransform(target, lop);
2098
+ const rotateX = target?.rotateX ?? lop.rotateX ?? 0;
2099
+ const rotateY = target?.rotateY ?? lop.rotateY ?? 0;
2100
+ const rotateZ = target?.rotateZ ?? lop.rotateZ ?? 0;
2101
+ const translateZ = target?.translateZ ?? lop.translateZ ?? 0;
2102
+ const perspective = target?.perspective ?? lop.perspective ?? 0;
2103
+ const perspectiveOriginX = target?.perspectiveOriginX ?? lop.perspectiveOriginX ?? lop.anchorX ?? .5;
2104
+ const perspectiveOriginY = target?.perspectiveOriginY ?? lop.perspectiveOriginY ?? lop.anchorY ?? .5;
2105
+ const backfaceVisibility = target?.backfaceVisibility ?? lop.backfaceVisibility ?? "visible";
2106
+ let quad3D = null;
2107
+ if (is3D) {
2108
+ const m3d = Transform3DMath.buildLayer3DMatrix({
2109
+ x: localX,
2110
+ y: localY,
2111
+ width: layerW,
2112
+ height: layerH,
2113
+ rotateXDeg: rotateX,
2114
+ rotateYDeg: rotateY,
2115
+ rotateZDeg: rotateZ !== 0 ? rotateZ : rotation,
2116
+ scaleX: scale$1,
2117
+ scaleY: scale$1,
2118
+ perspectivePx: perspective,
2119
+ originXRatio: perspectiveOriginX,
2120
+ originYRatio: perspectiveOriginY,
2121
+ translateZPx: translateZ
2122
+ });
2123
+ let q = Transform3DMath.projectRectangleCorners(m3d, layerW, layerH);
2124
+ if (!parentMatrix.isIdentity) q = {
2125
+ topLeft: parentMatrix.transformPoint(new DOMPoint(q.topLeft.x, q.topLeft.y)),
2126
+ topRight: parentMatrix.transformPoint(new DOMPoint(q.topRight.x, q.topRight.y)),
2127
+ bottomLeft: parentMatrix.transformPoint(new DOMPoint(q.bottomLeft.x, q.bottomLeft.y)),
2128
+ bottomRight: parentMatrix.transformPoint(new DOMPoint(q.bottomRight.x, q.bottomRight.y))
2129
+ };
2130
+ if (backfaceVisibility === "hidden" && Transform3DMath.computeSignedArea(q) < 0) return;
2131
+ quad3D = q;
2132
+ }
2133
+ const isTrue3D = Boolean(target?.is3D ?? lop.is3D) || Boolean(camera && ((target?.z ?? lop.z ?? 0) !== 0 || lop.is3D));
2134
+ if (isContainer) {
2135
+ if (lop.transformStyle === "preserve-3d" && isTrue3D) {
2136
+ let current3DMatrix = Matrix4Math.identity();
2137
+ const containerX = parent3DMatrix !== null ? target?.x ?? lop.x ?? 0 : target?.x ?? lop.x ?? localX + layerW * .5;
2138
+ const containerY = parent3DMatrix !== null ? target?.y ?? lop.y ?? 0 : target?.y ?? lop.y ?? localY + layerH * .5;
2139
+ const containerZ = target?.z ?? lop.z ?? 0;
2140
+ current3DMatrix = Matrix4Math.multiply(current3DMatrix, Matrix4Math.translate(containerX, containerY, containerZ));
2141
+ const rxRad = (target?.rotateX ?? lop.rotateX ?? 0) * Math.PI / 180;
2142
+ const ryRad = (target?.rotateY ?? lop.rotateY ?? 0) * Math.PI / 180;
2143
+ const rzRad = ((target?.rotateZ ?? lop.rotateZ ?? 0) !== 0 ? target?.rotateZ ?? lop.rotateZ ?? 0 : rotation) * (Math.PI / 180);
2144
+ if (rzRad !== 0) current3DMatrix = Matrix4Math.multiply(current3DMatrix, Matrix4Math.rotateZ(rzRad));
2145
+ if (ryRad !== 0) current3DMatrix = Matrix4Math.multiply(current3DMatrix, Matrix4Math.rotateY(ryRad));
2146
+ if (rxRad !== 0) current3DMatrix = Matrix4Math.multiply(current3DMatrix, Matrix4Math.rotateX(rxRad));
2147
+ const csX = (target?.scaleX ?? lop.scaleX ?? 1) * scale$1;
2148
+ const csY = (target?.scaleY ?? lop.scaleY ?? 1) * scale$1;
2149
+ const csZ = (target?.scaleZ ?? lop.scaleZ ?? 1) * scale$1;
2150
+ if (csX !== 1 || csY !== 1 || csZ !== 1) current3DMatrix = Matrix4Math.multiply(current3DMatrix, Matrix4Math.scale(csX, csY, csZ));
2151
+ if (parent3DMatrix) current3DMatrix = Matrix4Math.multiply(parent3DMatrix, current3DMatrix);
2152
+ if (nodeVM.children && nodeVM.children.length > 0) {
2153
+ const res = await renderNodeTree(nodeVM.children, activeTex, activeView, excludes, layerOpacity, new DOMMatrix(), null, layerW, layerH, current3DMatrix, false);
2154
+ activeTex = res.finalTex;
2155
+ activeView = res.finalView;
2156
+ }
2157
+ return {
2158
+ finalTex: activeTex,
2159
+ finalView: activeView
2160
+ };
2161
+ }
2162
+ if (isTrue3D) {
2163
+ const texW$1 = Math.max(1, Math.ceil(layerW));
2164
+ const texH$1 = Math.max(1, Math.ceil(layerH));
2165
+ const groupExcludes$1 = [
2166
+ activeTex,
2167
+ targetTexture,
2168
+ ...excludes,
2169
+ ...pending3DItems.map((q) => q.texture).filter((t) => Boolean(t))
2170
+ ];
2171
+ const groupTex$1 = ctx.renderer.getTemporaryTexture(texW$1, texH$1, groupExcludes$1);
2172
+ const groupView$1 = groupTex$1.createView();
2173
+ ctx.renderer.beginFrame(encoder, groupView$1, {
2174
+ r: 0,
2175
+ g: 0,
2176
+ b: 0,
2177
+ a: 0
2178
+ }, texW$1, texH$1, "clear").end();
2179
+ const br$2 = Math.min(lop.borderRadius ?? lop.strokeRadius ?? 0, layerW / 2, layerH / 2);
2180
+ if (shouldPaintContainerBackground(kind, lop.background)) {
2181
+ const bgPass = ctx.renderer.beginFrame(encoder, groupView$1, {
2182
+ r: 0,
2183
+ g: 0,
2184
+ b: 0,
2185
+ a: 0
2186
+ }, texW$1, texH$1, "load");
2187
+ ctx.renderer.drawRect(bgPass, {
2188
+ x: 0,
2189
+ y: 0,
2190
+ width: layerW,
2191
+ height: layerH
2192
+ }, lop.background, 0, { opacity: 1 });
2193
+ bgPass.end();
2194
+ }
2195
+ let activeGroupTex$1 = groupTex$1;
2196
+ let activeGroupView$1 = groupView$1;
2197
+ if (nodeVM.children && nodeVM.children.length > 0) {
2198
+ const res = await renderNodeTree(nodeVM.children, groupTex$1, groupView$1, [groupTex$1, ...groupExcludes$1], 1, new DOMMatrix(), rect, texW$1, texH$1, null, false);
2199
+ activeGroupTex$1 = res.finalTex;
2200
+ activeGroupView$1 = res.finalView;
2201
+ }
2202
+ const borderResult$2 = !isTrue3D && lop.borderWidth !== void 0 && lop.borderColor ? createBorderTexture(ctx, encoder, layerW, layerH, br$2, lop.borderWidth, lop.borderColor, lop.strokeAlign ?? "inside", [activeGroupTex$1, ...groupExcludes$1]) : null;
2203
+ if (borderResult$2) {
2204
+ const borderPass = ctx.renderer.beginFrame(encoder, activeGroupView$1, {
2205
+ r: 0,
2206
+ g: 0,
2207
+ b: 0,
2208
+ a: 0
2209
+ }, texW$1, texH$1, "load");
2210
+ ctx.renderer.drawTexture(borderPass, borderResult$2.borderTex, {
2211
+ x: borderResult$2.ox,
2212
+ y: borderResult$2.oy,
2213
+ width: borderResult$2.ow,
2214
+ height: borderResult$2.oh
2215
+ }, { opacity: 1 });
2216
+ borderPass.end();
2217
+ }
2218
+ let modelMatrix;
2219
+ if (parent3DMatrix) {
2220
+ let localM = Matrix4Math.identity();
2221
+ const childX = target?.x ?? lop.x ?? 0;
2222
+ const childY = target?.y ?? lop.y ?? 0;
2223
+ const childZ = target?.z ?? lop.z ?? 0;
2224
+ localM = Matrix4Math.multiply(localM, Matrix4Math.translate(childX, childY, childZ));
2225
+ const rxRad = (target?.rotateX ?? lop.rotateX ?? 0) * Math.PI / 180;
2226
+ const ryRad = (target?.rotateY ?? lop.rotateY ?? 0) * Math.PI / 180;
2227
+ const rzRad = ((target?.rotateZ ?? lop.rotateZ ?? 0) !== 0 ? target?.rotateZ ?? lop.rotateZ ?? 0 : rotation) * (Math.PI / 180);
2228
+ if (rzRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateZ(rzRad));
2229
+ if (ryRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateY(ryRad));
2230
+ if (rxRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateX(rxRad));
2231
+ const sx = layerW * scale$1;
2232
+ const sy = layerH * scale$1;
2233
+ const sz = target?.scaleZ ?? lop.scaleZ ?? 1;
2234
+ localM = Matrix4Math.multiply(localM, Matrix4Math.scale(sx, sy, sz));
2235
+ const ax = lop.anchorX ?? .5;
2236
+ const ay = lop.anchorY ?? .5;
2237
+ const pivotX = .5 - ax;
2238
+ const pivotY = .5 - ay;
2239
+ if (pivotX !== 0 || pivotY !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.translate(pivotX, pivotY, 0));
2240
+ modelMatrix = Matrix4Math.multiply(parent3DMatrix, localM);
2241
+ }
2242
+ const quadOpts = {
2243
+ position: [
2244
+ localX + layerW * .5,
2245
+ localY + layerH * .5,
2246
+ target?.z ?? lop.z ?? 0
2247
+ ],
2248
+ width: layerW,
2249
+ height: layerH,
2250
+ textureExtent: [layerW / activeGroupTex$1.width, layerH / activeGroupTex$1.height],
2251
+ rotation: [
2252
+ target?.rotateX ?? lop.rotateX ?? 0,
2253
+ target?.rotateY ?? lop.rotateY ?? 0,
2254
+ (target?.rotateZ ?? lop.rotateZ ?? 0) !== 0 ? target?.rotateZ ?? lop.rotateZ ?? 0 : rotation
2255
+ ],
2256
+ scale: [
2257
+ scale$1,
2258
+ scale$1,
2259
+ target?.scaleZ ?? lop.scaleZ ?? 1
2260
+ ],
2261
+ anchor: [lop.anchorX ?? .5, lop.anchorY ?? .5],
2262
+ borderRadius: br$2,
2263
+ borderWidth: lop.borderWidth,
2264
+ borderColor: lop.borderColor,
2265
+ opacity: layerOpacity,
2266
+ twoSided: target?.twoSided ?? lop.twoSided ?? true,
2267
+ material: target?.material ?? lop.material,
2268
+ shininess: target?.shininess ?? lop.shininess,
2269
+ roughness: target?.roughness ?? lop.roughness,
2270
+ specularIntensity: target?.specularIntensity ?? lop.specularIntensity,
2271
+ ambientIntensity: target?.ambientIntensity ?? lop.ambientIntensity,
2272
+ metallic: target?.metallic ?? lop.metallic,
2273
+ ...modelMatrix && { modelMatrix }
2274
+ };
2275
+ pending3DItems.push({
2276
+ kind: "quad",
2277
+ texture: activeGroupTex$1,
2278
+ opts: quadOpts
2279
+ });
2280
+ return {
2281
+ finalTex: activeTex,
2282
+ finalView: activeView
2283
+ };
2284
+ }
2285
+ if (is3D && quad3D) {
2286
+ const texW$1 = Math.max(1, Math.ceil(layerW));
2287
+ const texH$1 = Math.max(1, Math.ceil(layerH));
2288
+ const groupExcludes$1 = [
2289
+ activeTex,
2290
+ targetTexture,
2291
+ ...excludes
2292
+ ];
2293
+ const groupTex$1 = ctx.renderer.getTemporaryTexture(texW$1, texH$1, groupExcludes$1);
2294
+ const groupView$1 = groupTex$1.createView();
2295
+ ctx.renderer.beginFrame(encoder, groupView$1, {
2296
+ r: 0,
2297
+ g: 0,
2298
+ b: 0,
2299
+ a: 0
2300
+ }, texW$1, texH$1, "clear").end();
2301
+ const br$2 = Math.min(lop.borderRadius ?? lop.strokeRadius ?? 0, layerW / 2, layerH / 2);
2302
+ if (shouldPaintContainerBackground(kind, lop.background)) {
2303
+ const bgPass = ctx.renderer.beginFrame(encoder, groupView$1, {
2304
+ r: 0,
2305
+ g: 0,
2306
+ b: 0,
2307
+ a: 0
2308
+ }, texW$1, texH$1, "load");
2309
+ ctx.renderer.drawRect(bgPass, {
2310
+ x: 0,
2311
+ y: 0,
2312
+ width: layerW,
2313
+ height: layerH
2314
+ }, lop.background, br$2, { opacity: 1 });
2315
+ bgPass.end();
2316
+ }
2317
+ let activeGroupTex$1 = groupTex$1;
2318
+ let activeGroupView$1 = groupView$1;
2319
+ if (nodeVM.children && nodeVM.children.length > 0) {
2320
+ const res = await renderNodeTree(nodeVM.children, groupTex$1, groupView$1, [groupTex$1, ...groupExcludes$1], 1, new DOMMatrix(), rect, texW$1, texH$1, null, false);
2321
+ activeGroupTex$1 = res.finalTex;
2322
+ activeGroupView$1 = res.finalView;
2323
+ }
2324
+ const borderResult$2 = lop.borderWidth !== void 0 && lop.borderColor ? createBorderTexture(ctx, encoder, layerW, layerH, br$2, lop.borderWidth, lop.borderColor, lop.strokeAlign ?? "inside", [activeGroupTex$1, ...groupExcludes$1]) : null;
2325
+ if (borderResult$2) {
2326
+ const borderPass = ctx.renderer.beginFrame(encoder, activeGroupView$1, {
2327
+ r: 0,
2328
+ g: 0,
2329
+ b: 0,
2330
+ a: 0
2331
+ }, texW$1, texH$1, "load");
2332
+ ctx.renderer.drawTexture(borderPass, borderResult$2.borderTex, {
2333
+ x: borderResult$2.ox,
2334
+ y: borderResult$2.oy,
2335
+ width: borderResult$2.ow,
2336
+ height: borderResult$2.oh
2337
+ }, { opacity: 1 });
2338
+ borderPass.end();
2339
+ }
2340
+ const container3DShutter = target?.motionBlurShutter ?? lop.motionBlurShutter ?? 0;
2341
+ if (container3DShutter > 0 && fps && shiftedFrame !== void 0) {
2342
+ const currentT = shiftedFrame / fps;
2343
+ const prevT = (shiftedFrame - 1) / fps;
2344
+ tl.seek(prevT);
2345
+ const prevTarget = targetId ? targetsById[targetId] : void 0;
2346
+ const prevX = prevTarget?.x ?? baseX;
2347
+ const prevY = prevTarget?.y ?? baseY;
2348
+ tl.seek(currentT);
2349
+ const vx = sampledX - prevX;
2350
+ const vy = sampledY - prevY;
2351
+ if (Math.hypot(vx, vy) > .5) {
2352
+ activeGroupTex$1 = ctx.renderer.applyMotionBlur(encoder, activeGroupTex$1, vx, vy, container3DShutter, 16, [
2353
+ activeTex,
2354
+ targetTexture,
2355
+ ...excludes
2356
+ ]);
2357
+ activeGroupView$1 = activeGroupTex$1.createView();
2358
+ }
2359
+ }
2360
+ if (blendMode === "normal" || blendMode === "source-over") drawProjectedQuad(ctx, encoder, activeView, activeGroupTex$1, quad3D, targetW, targetH, layerOpacity);
2361
+ else {
2362
+ const compositeExcludes$1 = [
2363
+ activeTex,
2364
+ targetTexture,
2365
+ activeGroupTex$1,
2366
+ ...excludes
2367
+ ];
2368
+ if (borderResult$2) compositeExcludes$1.push(borderResult$2.borderTex);
2369
+ const layerTex = ctx.renderer.getTemporaryTexture(targetW, targetH, compositeExcludes$1);
2370
+ const layerView = layerTex.createView();
2371
+ ctx.renderer.beginFrame(encoder, layerView, {
2372
+ r: 0,
2373
+ g: 0,
2374
+ b: 0,
2375
+ a: 0
2376
+ }, targetW, targetH, "clear").end();
2377
+ drawProjectedQuad(ctx, encoder, layerView, activeGroupTex$1, quad3D, targetW, targetH, layerOpacity);
2378
+ activeTex = ctx.renderer.composite(encoder, activeTex, layerTex, blendMode, [targetTexture, ...excludes]);
2379
+ activeView = activeTex.createView();
2380
+ }
2381
+ return {
2382
+ finalTex: activeTex,
2383
+ finalView: activeView
2384
+ };
2385
+ }
2386
+ const shouldClip = lop.overflow !== "visible";
2387
+ const scissorRect = shouldClip ? computeContainerScissor(layerMatrix, layerW, layerH) : null;
2388
+ const clipRadius = shouldClip ? Math.min(target?.borderRadius ?? lop.borderRadius ?? 0, layerW / 2, layerH / 2) : 0;
2389
+ if ((kind === "flex" || kind === "block") && clipRadius <= 0 && !shouldPaintContainerBackground(kind, lop.background) && layerOpacity === 1 && blendMode === "normal" && !lop.borderWidth && !lop.borderColor && (typeof lop.relighting !== "object" || !lop.relighting) && (typeof lop.relight !== "object" || !lop.relight) && (typeof target?.relighting !== "object" || !target?.relighting) && (typeof target?.relight !== "object" || !target?.relight) && (typeof lop.deflicker !== "object" || !lop.deflicker) && (typeof target?.deflicker !== "object" || !target?.deflicker)) {
2390
+ if (scissorRect) ctx.renderer.pushScissor(scissorRect);
2391
+ const res = await renderNodeTree(nodeVM.children ?? [], activeTex, activeView, excludes, accumulatedOpacity, layerMatrix, rect, targetW, targetH, null, isRootScene);
2392
+ if (scissorRect) ctx.renderer.popScissor();
2393
+ return res;
2394
+ }
2395
+ const groupExcludes = [
2396
+ activeTex,
2397
+ targetTexture,
2398
+ ...excludes
2399
+ ];
2400
+ const groupTex = ctx.renderer.getTemporaryTexture(targetW, targetH, groupExcludes);
2401
+ const groupView = groupTex.createView();
2402
+ ctx.renderer.beginFrame(encoder, groupView, {
2403
+ r: 0,
2404
+ g: 0,
2405
+ b: 0,
2406
+ a: 0
2407
+ }, targetW, targetH, "clear").end();
2408
+ const br$1 = Math.min(target?.borderRadius ?? lop.borderRadius ?? lop.strokeRadius ?? 0, layerW / 2, layerH / 2);
2409
+ if (shouldPaintContainerBackground(kind, lop.background)) {
2410
+ const bgPass = ctx.renderer.beginFrame(encoder, groupView, {
2411
+ r: 0,
2412
+ g: 0,
2413
+ b: 0,
2414
+ a: 0
2415
+ }, targetW, targetH, "load");
2416
+ ctx.renderer.pushTransform(layerMatrix);
2417
+ ctx.renderer.drawRect(bgPass, {
2418
+ x: 0,
2419
+ y: 0,
2420
+ width: layerW,
2421
+ height: layerH
2422
+ }, lop.background, br$1, { opacity: layerOpacity });
2423
+ ctx.renderer.popTransform();
2424
+ bgPass.end();
2425
+ }
2426
+ let activeGroupTex = groupTex;
2427
+ let activeGroupView = groupView;
2428
+ if (nodeVM.children && nodeVM.children.length > 0) {
2429
+ if (scissorRect) ctx.renderer.pushScissor(scissorRect);
2430
+ const res = await renderNodeTree(nodeVM.children, groupTex, groupView, [groupTex, ...groupExcludes], layerOpacity, layerMatrix, rect, targetW, targetH, null, false);
2431
+ if (scissorRect) ctx.renderer.popScissor();
2432
+ activeGroupTex = res.finalTex;
2433
+ activeGroupView = res.finalView;
2434
+ }
2435
+ if (clipRadius > 0) {
2436
+ const maskTex = ctx.renderer.getTemporaryTexture(targetW, targetH, [activeGroupTex, ...groupExcludes]);
2437
+ const maskPass = ctx.renderer.beginFrame(encoder, maskTex.createView(), {
2438
+ r: 0,
2439
+ g: 0,
2440
+ b: 0,
2441
+ a: 0
2442
+ }, targetW, targetH, "clear");
2443
+ ctx.renderer.pushTransform(layerMatrix);
2444
+ ctx.renderer.drawRect(maskPass, {
2445
+ x: 0,
2446
+ y: 0,
2447
+ width: layerW,
2448
+ height: layerH
2449
+ }, "#ffffff", clipRadius);
2450
+ ctx.renderer.popTransform();
2451
+ maskPass.end();
2452
+ activeGroupTex = ctx.renderer.composite(encoder, activeGroupTex, maskTex, "mask-in", [
2453
+ activeGroupTex,
2454
+ maskTex,
2455
+ ...groupExcludes
2456
+ ]);
2457
+ activeGroupView = activeGroupTex.createView();
2458
+ }
2459
+ const borderResult$1 = lop.borderWidth !== void 0 && lop.borderColor ? createBorderTexture(ctx, encoder, layerW, layerH, br$1, lop.borderWidth, lop.borderColor, lop.strokeAlign ?? "inside", [activeGroupTex, ...groupExcludes]) : null;
2460
+ if (borderResult$1) {
2461
+ const borderPass = ctx.renderer.beginFrame(encoder, activeGroupView, {
2462
+ r: 0,
2463
+ g: 0,
2464
+ b: 0,
2465
+ a: 0
2466
+ }, targetW, targetH, "load");
2467
+ ctx.renderer.pushTransform(layerMatrix);
2468
+ ctx.renderer.drawTexture(borderPass, borderResult$1.borderTex, {
2469
+ x: borderResult$1.ox,
2470
+ y: borderResult$1.oy,
2471
+ width: borderResult$1.ow,
2472
+ height: borderResult$1.oh
2473
+ }, { opacity: layerOpacity });
2474
+ ctx.renderer.popTransform();
2475
+ borderPass.end();
2476
+ }
2477
+ const containerShutter = target?.motionBlurShutter ?? lop.motionBlurShutter ?? 0;
2478
+ if (containerShutter > 0 && fps && shiftedFrame !== void 0) {
2479
+ const currentT = shiftedFrame / fps;
2480
+ const prevT = (shiftedFrame - 1) / fps;
2481
+ tl.seek(prevT);
2482
+ const prevTarget = targetId ? targetsById[targetId] : void 0;
2483
+ const prevX = prevTarget?.x ?? baseX;
2484
+ const prevY = prevTarget?.y ?? baseY;
2485
+ tl.seek(currentT);
2486
+ const vx = sampledX - prevX;
2487
+ const vy = sampledY - prevY;
2488
+ if (Math.hypot(vx, vy) > .5) {
2489
+ activeGroupTex = ctx.renderer.applyMotionBlur(encoder, activeGroupTex, vx, vy, containerShutter, 16, [
2490
+ activeTex,
2491
+ targetTexture,
2492
+ ...excludes
2493
+ ]);
2494
+ activeGroupView = activeGroupTex.createView();
2495
+ }
2496
+ }
2497
+ const deflickerFromEffects = Array.isArray(lop.effects) ? lop.effects.find((e) => e?.op === "TemporalDeflicker" || e?.op === "Deflicker" || e?.constructor?.name === "TemporalDeflicker") : void 0;
2498
+ const rawDeflicker = target?.deflicker ?? lop.deflicker ?? deflickerFromEffects;
2499
+ const deflickerOpts$1 = typeof rawDeflicker === "object" && rawDeflicker !== null ? rawDeflicker : void 0;
2500
+ if (deflickerOpts$1) {
2501
+ const deflickerPipe = getTemporalDeflickerPipeline(ctx.device, lop.id || `layer_${i}`);
2502
+ if (shiftedFrame === 0) deflickerPipe.reset();
2503
+ activeGroupTex = deflickerPipe.execute(activeGroupTex, deflickerOpts$1, encoder);
2504
+ activeGroupView = activeGroupTex.createView();
2505
+ }
2506
+ const relightEffect = Array.isArray(lop.effects) ? lop.effects.find((e) => e?.op === "Relight3D" || e?.constructor?.name === "Relight3D") : void 0;
2507
+ const rawRelight = target?.relighting ?? lop.relighting ?? (typeof lop.relight === "object" ? lop.relight : void 0) ?? relightEffect;
2508
+ const relightOpts$1 = typeof rawRelight === "object" && rawRelight !== null ? rawRelight : void 0;
2509
+ if (relightOpts$1) {
2510
+ const relightPipeline = getScreenSpaceRelightPipeline(ctx.device, ctx.renderer.format ?? "rgba8unorm");
2511
+ const r3dInstance = getRenderer3D(ctx.device, ctx.renderer.format ?? "rgba8unorm", sampleCount3d);
2512
+ const activeLights = synthesizeRelightLights(relightOpts$1, sceneLights, rect, shiftedFrame, fps ?? 24);
2513
+ const lightsBuffer = r3dInstance.getOrCreateLightsBuffer(activeLights);
2514
+ const relitTex = ctx.renderer.getTemporaryTexture(targetW, targetH, [
2515
+ activeGroupTex,
2516
+ activeTex,
2517
+ targetTexture,
2518
+ ...excludes
2519
+ ]);
2520
+ const relitView = relitTex.createView();
2521
+ const normalTex = relightOpts$1.normalTexture ?? relightOpts$1.normalMap;
2522
+ relightPipeline.execute(encoder, relitView, activeGroupTex, normalTex, lightsBuffer, {
2523
+ viewportWidth: targetW,
2524
+ viewportHeight: targetH,
2525
+ layerX: 0,
2526
+ layerY: 0,
2527
+ roughness: resolveVal(target?.roughness ?? target?.relightRoughness ?? relightOpts$1.specularRoughness ?? relightOpts$1.roughness, .35, shiftedFrame, fps ?? 24),
2528
+ specularStrength: resolveVal(target?.specularStrength ?? target?.relightSpecularStrength ?? relightOpts$1.specularStrength, .7, shiftedFrame, fps ?? 24),
2529
+ metallic: resolveVal(target?.metallic ?? target?.relightMetallic ?? relightOpts$1.metallic, 0, shiftedFrame, fps ?? 24),
2530
+ ambientIntensity: resolveVal(target?.ambientIntensity ?? target?.relightAmbientIntensity ?? relightOpts$1.ambientIntensity, .15, shiftedFrame, fps ?? 24),
2531
+ depthScale: resolveVal(target?.depthScale ?? target?.relightDepthScale ?? relightOpts$1.depthScale, 1, shiftedFrame, fps ?? 24),
2532
+ opacity: 1
2533
+ }, "clear");
2534
+ activeGroupTex = relitTex;
2535
+ activeGroupView = relitView;
2536
+ }
2537
+ const compositeExcludes = [
2538
+ activeTex,
2539
+ targetTexture,
2540
+ ...excludes
2541
+ ];
2542
+ if (borderResult$1) compositeExcludes.push(borderResult$1.borderTex);
2543
+ activeTex = ctx.renderer.composite(encoder, activeTex, activeGroupTex, blendMode, compositeExcludes);
2544
+ activeView = activeTex.createView();
2545
+ return {
2546
+ finalTex: activeTex,
2547
+ finalView: activeView
2548
+ };
2549
+ }
2550
+ if (!(layerW > 0 && layerH > 0)) return {
2551
+ finalTex: activeTex,
2552
+ finalView: activeView
2553
+ };
2554
+ const br = Math.min(lop.borderRadius ?? lop.strokeRadius ?? 0, layerW / 2, layerH / 2);
2555
+ const texW = Math.ceil(layerW);
2556
+ const texH = Math.ceil(layerH);
2557
+ const savedScissors = ctx.renderer.suspendScissors?.() ?? [];
2558
+ const localTex = ctx.renderer.getTemporaryTexture(texW, texH, [
2559
+ activeTex,
2560
+ targetTexture,
2561
+ ...excludes
2562
+ ]);
2563
+ const localView = localTex.createView();
2564
+ ctx.renderer.beginFrame(encoder, localView, {
2565
+ r: 0,
2566
+ g: 0,
2567
+ b: 0,
2568
+ a: 0
2569
+ }, texW, texH, "clear").end();
2570
+ let content;
2571
+ let isCaption = false;
2572
+ if (kind === "media") {
2573
+ content = nodeVM.children?.[0];
2574
+ isCaption = content?.operation?.dataType === "Caption" || lop.dataType === "Caption" || (content?.operation)?.srtText !== void 0;
2575
+ } else if (kind === "text") {
2576
+ const fullText = lop.text ?? "";
2577
+ let displayText = fullText;
2578
+ let resolvedAnimators = rawAnimators;
2579
+ if (Array.isArray(rawAnimators) && rawAnimators.length > 0) resolvedAnimators = rawAnimators.map((animator, idx) => {
2580
+ const a = {
2581
+ ...animator,
2582
+ transform: { ...animator.transform ?? {} }
2583
+ };
2584
+ if (idx === 0) {
2585
+ const offsetVal = typeof target?.offset === "number" ? target.offset : typeof target?.animatorOffset === "number" ? target.animatorOffset : void 0;
2586
+ const rangeStartVal = typeof target?.rangeStart === "number" ? target.rangeStart : typeof target?.animatorRangeStart === "number" ? target.animatorRangeStart : void 0;
2587
+ const rangeEndVal = typeof target?.rangeEnd === "number" ? target.rangeEnd : typeof target?.animatorRangeEnd === "number" ? target.animatorRangeEnd : void 0;
2588
+ if (offsetVal !== void 0) a.offset = offsetVal;
2589
+ if (rangeStartVal !== void 0) a.rangeStart = rangeStartVal;
2590
+ if (rangeEndVal !== void 0) a.rangeEnd = rangeEndVal;
2591
+ if (Array.isArray(a.selectors)) a.selectors = a.selectors.map((s) => {
2592
+ if (s.type === "range") return {
2593
+ ...s,
2594
+ ...offsetVal !== void 0 ? { offset: offsetVal } : {},
2595
+ ...rangeStartVal !== void 0 ? { start: rangeStartVal } : {},
2596
+ ...rangeEndVal !== void 0 ? { end: rangeEndVal } : {}
2597
+ };
2598
+ return s;
2599
+ });
2600
+ }
2601
+ return a;
2602
+ });
2603
+ const hasKineticAnimators$1 = Array.isArray(resolvedAnimators) && resolvedAnimators.length > 0;
2604
+ const hasTypewriter = Boolean(lop.typewriter || target?.typewriter);
2605
+ const textTrack = lop.animation?.tracks?.find((t) => t.prop === "text");
2606
+ if (!hasKineticAnimators$1 && !hasTypewriter && (textTrack !== void 0 || typeof target?.text === "number") && fullText.length > 0) {
2607
+ const progress = typeof target?.text === "number" ? Math.max(0, Math.min(1, target.text)) : 1;
2608
+ if (progress >= 1) displayText = fullText;
2609
+ else if (progress <= 0) displayText = "";
2610
+ else {
2611
+ const presetType = textTrack?.keyframes?.find((k) => k.presetType)?.presetType ?? "typewriter";
2612
+ const textNumeric = typeof target?.text === "number" ? target.text : null;
2613
+ const isDirectCount = textNumeric !== null && textNumeric > 1.0001;
2614
+ if (presetType === "word-reveal" || presetType === "karaoke") {
2615
+ const tokens = fullText.split(/(\s+)/);
2616
+ const wordIndices = [];
2617
+ tokens.forEach((token, idx) => {
2618
+ if (token.trim().length > 0) wordIndices.push(idx);
2619
+ });
2620
+ const visibleWordCount = isDirectCount && textNumeric !== null ? Math.min(wordIndices.length, Math.round(textNumeric)) : Math.round(wordIndices.length * progress);
2621
+ if (visibleWordCount === 0) displayText = "";
2622
+ else {
2623
+ const lastTokenIdx = wordIndices[Math.min(wordIndices.length - 1, visibleWordCount - 1)];
2624
+ displayText = tokens.slice(0, lastTokenIdx + 1).join("");
2625
+ }
2626
+ } else if (presetType === "line-reveal") {
2627
+ const lines = fullText.split("\n");
2628
+ const visibleLineCount = isDirectCount && textNumeric !== null ? Math.min(lines.length, Math.round(textNumeric)) : Math.round(lines.length * progress);
2629
+ displayText = lines.slice(0, Math.max(0, visibleLineCount)).join("\n");
2630
+ } else {
2631
+ const visibleChars = isDirectCount && textNumeric !== null ? Math.min(fullText.length, Math.round(textNumeric)) : Math.round(fullText.length * progress);
2632
+ displayText = fullText.slice(0, visibleChars);
2633
+ }
2634
+ }
2635
+ }
2636
+ content = {
2637
+ metadata: {},
2638
+ operation: {
2639
+ op: "text",
2640
+ text: displayText,
2641
+ animators: resolvedAnimators,
2642
+ pathOptions: target?.pathOptions ?? lop.pathOptions,
2643
+ typewriter: target?.typewriter ?? lop.typewriter,
2644
+ marquee: target?.marquee ?? lop.marquee,
2645
+ textProgress: typeof target?.text === "number" ? target.text : void 0,
2646
+ firstMargin: target?.firstMargin ?? lop.firstMargin,
2647
+ lastMargin: target?.lastMargin ?? lop.lastMargin,
2648
+ baselineShift: target?.baselineShift ?? lop.baselineShift,
2649
+ signals: virtualMedia?.operation?.signals ?? virtualMedia?.signals,
2650
+ fontSize: target?.fontSize ?? lop.fontSize,
2651
+ fontFamily: lop.fontFamily,
2652
+ fontStyle: lop.fontStyle,
2653
+ fontWeight: lop.fontWeight,
2654
+ fill: target?.fill ?? target?.color ?? lop.fill,
2655
+ align: lop.align === "start" ? "left" : lop.align === "end" ? "right" : lop.align,
2656
+ verticalAlign: lop.verticalAlign,
2657
+ letterSpacing: target?.letterSpacing ?? lop.letterSpacing,
2658
+ lineHeight: lop.lineHeight,
2659
+ padding: lop.padding,
2660
+ textBackgroundColor: target?.textBackgroundColor ?? target?.background ?? lop.background,
2661
+ borderRadius: lop.borderRadius,
2662
+ stroke: target?.stroke ?? target?.strokeColor ?? lop.stroke,
2663
+ strokeWidth: target?.strokeWidth ?? lop.strokeWidth,
2664
+ strokeAlign: lop.strokeAlign,
2665
+ textShadow: lop.textShadow,
2666
+ shadows: lop.shadows,
2667
+ x: isPathText ? 0 : padX,
2668
+ y: isPathText ? 0 : padY,
2669
+ width: isPathText ? targetW : lop.width !== void 0 ? rect.width : void 0,
2670
+ height: isPathText ? targetH : lop.height !== void 0 ? rect.height : void 0
2671
+ },
2672
+ children: []
2673
+ };
2674
+ }
2675
+ let glyphBatches = null;
2676
+ let layerHasRaster = true;
2677
+ if (content) {
2678
+ const contentDurationMs = getActiveMediaMetadata(content)?.durationMs ?? composeDuration;
2679
+ const layerDurationInMS = Math.max(1, Math.round(contentDurationMs / 1e3 * (fps ?? 24))) / (fps ?? 24) * 1e3;
2680
+ const contentIsImageCompositor = content.operation?.op === "Compositor" && content.operation?.dataType === "Image";
2681
+ const captureGlyphs = isTrue3D && kind === "text";
2682
+ const rasterDrawsBefore = ctx.renderer.rasterDrawCount;
2683
+ if (captureGlyphs) ctx.renderer.slugPipeline.beginCapture();
2684
+ try {
2685
+ await drawChild(content, {
2686
+ containerWidth: layerW,
2687
+ containerHeight: layerH,
2688
+ renderId: `${props.renderId}-l${i}`,
2689
+ durationMs: layerDurationInMS,
2690
+ opacity: 1,
2691
+ fps,
2692
+ excludeTextures: [
2693
+ activeTex,
2694
+ targetTexture,
2695
+ ...excludes
2696
+ ],
2697
+ isVideoMode: isVideoMode && !contentIsImageCompositor,
2698
+ ...isVideoMode && { frame: Math.max(0, shiftedFrame - (lop.startFrame ?? 0)) + (lop.mediaStartFrame ?? lop.trimStartFrames ?? (lop.trimStartSec != null ? Math.round(lop.trimStartSec * (fps ?? 24)) : 0)) },
2699
+ ...isCaption && {
2700
+ fontFamily: lop.fontFamily,
2701
+ fontSize: target?.fontSize ?? lop.fontSize,
2702
+ fontWeight: lop.fontWeight,
2703
+ fontStyle: lop.fontStyle,
2704
+ fill: lop.fill,
2705
+ color: lop.fill,
2706
+ align: lop.align === "start" ? "left" : lop.align === "end" ? "right" : lop.align ?? "center",
2707
+ verticalAlign: lop.verticalAlign ?? lop.textAlignVertical,
2708
+ lineHeight: lop.lineHeight,
2709
+ letterSpacing: lop.letterSpacing,
2710
+ textBackgroundColor: lop.background ?? lop.textBackgroundColor,
2711
+ borderRadius: lop.borderRadius ?? lop.strokeRadius,
2712
+ strokeRadius: lop.strokeRadius ?? lop.borderRadius,
2713
+ stroke: lop.stroke,
2714
+ strokeWidth: lop.strokeWidth,
2715
+ shadows: lop.shadows,
2716
+ padding: lop.padding,
2717
+ maxWidth: lop.maxWidth ?? layerW
2718
+ }
2719
+ }, localView, localTex, texW, texH);
2720
+ } finally {
2721
+ if (captureGlyphs) glyphBatches = ctx.renderer.slugPipeline.endCapture();
2722
+ }
2723
+ layerHasRaster = ctx.renderer.rasterDrawCount !== rasterDrawsBefore;
2724
+ } else if (kind === "shape") {
2725
+ const shapePass = ctx.renderer.beginFrame(encoder, localView, {
2726
+ r: 0,
2727
+ g: 0,
2728
+ b: 0,
2729
+ a: 0
2730
+ }, texW, texH, "load");
2731
+ const shapeConfig = {
2732
+ shapeType: lop.shapeType ?? lop.shape ?? "rect",
2733
+ width: layerW,
2734
+ height: layerH,
2735
+ path: lop.d ?? lop.path,
2736
+ cornerRadius: target?.cornerRadius ?? lop.cornerRadius ?? lop.borderRadius ?? lop.radius ?? 0,
2737
+ starPoints: lop.starPoints ?? 5,
2738
+ starInnerRadiusRatio: target?.starInnerRadiusRatio ?? lop.starInnerRadiusRatio ?? .5,
2739
+ polygonSides: lop.polygonSides ?? 5
2740
+ };
2741
+ const fillType = lop.fillType ?? "solid";
2742
+ const fillColor = target?.fillColor ?? target?.fill ?? lop.fillColor ?? lop.fill ?? "#38bdf8";
2743
+ const color2 = target?.gradientEndColor ?? lop.gradientEndColor ?? lop.fillColor2 ?? "#9333ea";
2744
+ let startX = lop.fillStartX;
2745
+ let startY = lop.fillStartY;
2746
+ let endX = lop.fillEndX;
2747
+ let endY = lop.fillEndY;
2748
+ if (fillType === "linear" && startX === void 0 && lop.gradientAngle !== void 0) {
2749
+ const rad = lop.gradientAngle * Math.PI / 180;
2750
+ const cx = layerW / 2;
2751
+ const cy = layerH / 2;
2752
+ const half = (Math.abs(layerW * Math.cos(rad)) + Math.abs(layerH * Math.sin(rad))) / 2;
2753
+ startX = cx - Math.cos(rad) * half;
2754
+ startY = cy - Math.sin(rad) * half;
2755
+ endX = cx + Math.cos(rad) * half;
2756
+ endY = cy + Math.sin(rad) * half;
2757
+ }
2758
+ const fillConfig = {
2759
+ type: fillType,
2760
+ color: fillColor,
2761
+ color2,
2762
+ startX,
2763
+ startY,
2764
+ endX,
2765
+ endY,
2766
+ cx: lop.fillCx,
2767
+ cy: lop.fillCy,
2768
+ radius: lop.fillRadius
2769
+ };
2770
+ const strokeWidth = target?.strokeWidth ?? lop.strokeWidth ?? 0;
2771
+ const strokeColor = target?.strokeColor ?? lop.strokeColor ?? lop.stroke ?? "#ffffff";
2772
+ const strokeLineCap = lop.strokeLineCap ?? "round";
2773
+ const strokeDashArray = lop.strokeDashArray;
2774
+ const strokeDashOffset = target?.strokeDashOffset ?? lop.strokeDashOffset ?? 0;
2775
+ const trimStart = target?.trimStart ?? lop.trimStart ?? 0;
2776
+ const trimEnd = target?.trimEnd ?? lop.trimEnd ?? 1;
2777
+ const trimOffset = target?.trimOffset ?? lop.trimOffset ?? 0;
2778
+ ctx.renderer.drawShape(shapePass, shapeConfig, fillConfig, strokeWidth > 0 ? {
2779
+ color: strokeColor,
2780
+ width: strokeWidth,
2781
+ cap: strokeLineCap,
2782
+ trimStart,
2783
+ trimEnd,
2784
+ trimOffset,
2785
+ dashArray: strokeDashArray,
2786
+ dashOffset: strokeDashOffset
2787
+ } : void 0);
2788
+ shapePass.end();
2789
+ }
2790
+ const leafDeflickerEffect = Array.isArray(lop.effects) ? lop.effects.find((e) => e?.op === "TemporalDeflicker" || e?.constructor?.name === "TemporalDeflicker") : void 0;
2791
+ const deflickerOpts = target?.deflicker ?? lop.deflicker ?? leafDeflickerEffect;
2792
+ const leafRelightEffect = Array.isArray(lop.effects) ? lop.effects.find((e) => e?.op === "Relight3D" || e?.constructor?.name === "Relight3D") : void 0;
2793
+ const rawLeafRelight = target?.relighting ?? lop.relighting ?? (typeof lop.relight === "object" ? lop.relight : void 0) ?? leafRelightEffect;
2794
+ const relightOpts = typeof rawLeafRelight === "object" && rawLeafRelight !== null ? rawLeafRelight : void 0;
2795
+ const shutterAngle = target?.motionBlurShutter ?? lop.motionBlurShutter ?? 0;
2796
+ if (glyphBatches && glyphBatches.length > 0 && (deflickerOpts || relightOpts || shutterAngle > 0)) {
2797
+ const glyphPass = ctx.renderer.beginFrame(encoder, localView, {
2798
+ r: 0,
2799
+ g: 0,
2800
+ b: 0,
2801
+ a: 0
2802
+ }, texW, texH, "load");
2803
+ ctx.renderer.slugPipeline.drawBatches(glyphPass, glyphBatches, texW, texH);
2804
+ glyphPass.end();
2805
+ glyphBatches = null;
2806
+ layerHasRaster = true;
2807
+ }
2808
+ let finalTex = localTex;
2809
+ if (!isTrue3D && br > 0 && !isCaption && kind !== "shape") {
2810
+ const maskTex = ctx.renderer.getTemporaryTexture(texW, texH, [
2811
+ localTex,
2812
+ activeTex,
2813
+ targetTexture,
2814
+ ...excludes
2815
+ ]);
2816
+ const maskView = maskTex.createView();
2817
+ ctx.renderer.beginFrame(encoder, maskView, {
2818
+ r: 0,
2819
+ g: 0,
2820
+ b: 0,
2821
+ a: 0
2822
+ }, texW, texH, "clear").end();
2823
+ const maskPass = ctx.renderer.beginFrame(encoder, maskView, {
2824
+ r: 0,
2825
+ g: 0,
2826
+ b: 0,
2827
+ a: 0
2828
+ }, texW, texH, "load");
2829
+ ctx.renderer.drawRect(maskPass, {
2830
+ x: 0,
2831
+ y: 0,
2832
+ width: layerW,
2833
+ height: layerH
2834
+ }, {
2835
+ r: 1,
2836
+ g: 1,
2837
+ b: 1,
2838
+ a: 1
2839
+ }, br);
2840
+ maskPass.end();
2841
+ finalTex = ctx.renderer.composite(encoder, localTex, maskTex, "mask-in", [
2842
+ activeTex,
2843
+ targetTexture,
2844
+ ...excludes
2845
+ ]);
2846
+ }
2847
+ if (deflickerOpts) {
2848
+ const deflickerPipe = getTemporalDeflickerPipeline(ctx.device, lop.id || `layer_${i}`);
2849
+ if (shiftedFrame === 0) deflickerPipe.reset();
2850
+ finalTex = deflickerPipe.execute(finalTex, deflickerOpts, encoder);
2851
+ }
2852
+ if (relightOpts) {
2853
+ const relightPipeline = getScreenSpaceRelightPipeline(ctx.device, ctx.renderer.format ?? "rgba8unorm");
2854
+ const r3dInstance = getRenderer3D(ctx.device, ctx.renderer.format ?? "rgba8unorm", sampleCount3d);
2855
+ const activeLights = synthesizeRelightLights(relightOpts, sceneLights, {
2856
+ x: sampledX,
2857
+ y: sampledY,
2858
+ width: texW,
2859
+ height: texH
2860
+ }, shiftedFrame, fps ?? 24);
2861
+ const lightsBuffer = r3dInstance.getOrCreateLightsBuffer(activeLights);
2862
+ const relitTex = ctx.renderer.getTemporaryTexture(texW, texH, [
2863
+ finalTex,
2864
+ activeTex,
2865
+ targetTexture,
2866
+ ...excludes
2867
+ ]);
2868
+ const relitView = relitTex.createView();
2869
+ const normalTex = relightOpts.normalTexture ?? relightOpts.normalMap;
2870
+ relightPipeline.execute(encoder, relitView, finalTex, normalTex, lightsBuffer, {
2871
+ viewportWidth: texW,
2872
+ viewportHeight: texH,
2873
+ layerX: sampledX,
2874
+ layerY: sampledY,
2875
+ roughness: resolveVal(target?.roughness ?? target?.relightRoughness ?? relightOpts.specularRoughness ?? relightOpts.roughness, .35, shiftedFrame, fps ?? 24),
2876
+ specularStrength: resolveVal(target?.specularStrength ?? target?.relightSpecularStrength ?? relightOpts.specularStrength, .7, shiftedFrame, fps ?? 24),
2877
+ metallic: resolveVal(target?.metallic ?? target?.relightMetallic ?? relightOpts.metallic, 0, shiftedFrame, fps ?? 24),
2878
+ ambientIntensity: resolveVal(target?.ambientIntensity ?? target?.relightAmbientIntensity ?? relightOpts.ambientIntensity, .15, shiftedFrame, fps ?? 24),
2879
+ depthScale: resolveVal(target?.depthScale ?? target?.relightDepthScale ?? relightOpts.depthScale, 1, shiftedFrame, fps ?? 24),
2880
+ opacity: 1
2881
+ }, "clear");
2882
+ finalTex = relitTex;
2883
+ }
2884
+ let blurPadX = 0;
2885
+ let blurPadY = 0;
2886
+ if (shutterAngle > 0 && fps && shiftedFrame !== void 0) {
2887
+ const currentT = shiftedFrame / fps;
2888
+ const prevT = (shiftedFrame - 1) / fps;
2889
+ tl.seek(prevT);
2890
+ const prevTarget = targetId ? targetsById[targetId] : void 0;
2891
+ const prevX = prevTarget?.x ?? baseX;
2892
+ const prevY = prevTarget?.y ?? baseY;
2893
+ tl.seek(currentT);
2894
+ const vx = sampledX - prevX;
2895
+ const vy = sampledY - prevY;
2896
+ if (Math.hypot(vx, vy) > .5) {
2897
+ if (!isTrue3D && !(is3D && quad3D)) {
2898
+ const reach = shutterAngle / 360;
2899
+ blurPadX = Math.ceil(Math.abs(vx) * reach);
2900
+ blurPadY = Math.ceil(Math.abs(vy) * reach);
2901
+ const paddedTex = ctx.renderer.getTemporaryTexture(texW + blurPadX * 2, texH + blurPadY * 2, [
2902
+ finalTex,
2903
+ activeTex,
2904
+ targetTexture,
2905
+ ...excludes
2906
+ ]);
2907
+ const padPass = ctx.renderer.beginFrame(encoder, paddedTex.createView(), {
2908
+ r: 0,
2909
+ g: 0,
2910
+ b: 0,
2911
+ a: 0
2912
+ }, paddedTex.width, paddedTex.height, "clear");
2913
+ ctx.renderer.drawTexture(padPass, finalTex, {
2914
+ x: blurPadX,
2915
+ y: blurPadY,
2916
+ width: texW,
2917
+ height: texH
2918
+ }, { opacity: 1 });
2919
+ padPass.end();
2920
+ finalTex = paddedTex;
2921
+ }
2922
+ const streakPx = Math.hypot(vx, vy) * (shutterAngle / 360) * 2;
2923
+ const samples = Math.min(128, Math.max(16, Math.ceil(streakPx / 2)));
2924
+ finalTex = ctx.renderer.applyMotionBlur(encoder, finalTex, vx, vy, shutterAngle, samples, [
2925
+ activeTex,
2926
+ targetTexture,
2927
+ ...excludes
2928
+ ]);
2929
+ }
2930
+ }
2931
+ const borderExcludes = [
2932
+ localTex,
2933
+ finalTex,
2934
+ activeTex,
2935
+ targetTexture,
2936
+ ...excludes
2937
+ ];
2938
+ const borderResult = !isTrue3D && lop.borderWidth !== void 0 && lop.borderColor ? createBorderTexture(ctx, encoder, layerW, layerH, br, lop.borderWidth, lop.borderColor, lop.strokeAlign ?? "inside", borderExcludes) : null;
2939
+ if (savedScissors.length > 0) ctx.renderer.resumeScissors?.(savedScissors);
2940
+ if (isTrue3D) {
2941
+ const renderedTex = finalTex;
2942
+ if (borderResult) {
2943
+ const borderPass = ctx.renderer.beginFrame(encoder, renderedTex.createView(), {
2944
+ r: 0,
2945
+ g: 0,
2946
+ b: 0,
2947
+ a: 0
2948
+ }, texW, texH, "load");
2949
+ ctx.renderer.drawTexture(borderPass, borderResult.borderTex, {
2950
+ x: borderResult.ox,
2951
+ y: borderResult.oy,
2952
+ width: borderResult.ow,
2953
+ height: borderResult.oh
2954
+ }, { opacity: 1 });
2955
+ borderPass.end();
2956
+ }
2957
+ let modelMatrix;
2958
+ if (parent3DMatrix) {
2959
+ let localM = Matrix4Math.identity();
2960
+ const childX = target?.x ?? lop.x ?? 0;
2961
+ const childY = target?.y ?? lop.y ?? 0;
2962
+ const childZ = target?.z ?? lop.z ?? 0;
2963
+ localM = Matrix4Math.multiply(localM, Matrix4Math.translate(childX, childY, childZ));
2964
+ const rxRad = (target?.rotateX ?? lop.rotateX ?? 0) * Math.PI / 180;
2965
+ const ryRad = (target?.rotateY ?? lop.rotateY ?? 0) * Math.PI / 180;
2966
+ const rzRad = ((target?.rotateZ ?? lop.rotateZ ?? 0) !== 0 ? target?.rotateZ ?? lop.rotateZ ?? 0 : rotation) * (Math.PI / 180);
2967
+ if (rzRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateZ(rzRad));
2968
+ if (ryRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateY(ryRad));
2969
+ if (rxRad !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.rotateX(rxRad));
2970
+ const sx = layerW * scale$1;
2971
+ const sy = layerH * scale$1;
2972
+ const sz = target?.scaleZ ?? lop.scaleZ ?? 1;
2973
+ localM = Matrix4Math.multiply(localM, Matrix4Math.scale(sx, sy, sz));
2974
+ const ax = lop.anchorX ?? .5;
2975
+ const ay = lop.anchorY ?? .5;
2976
+ const pivotX = .5 - ax;
2977
+ const pivotY = .5 - ay;
2978
+ if (pivotX !== 0 || pivotY !== 0) localM = Matrix4Math.multiply(localM, Matrix4Math.translate(pivotX, pivotY, 0));
2979
+ modelMatrix = Matrix4Math.multiply(parent3DMatrix, localM);
2980
+ }
2981
+ const quadOpts = {
2982
+ position: [
2983
+ localX + layerW * .5,
2984
+ localY + layerH * .5,
2985
+ target?.z ?? lop.z ?? 0
2986
+ ],
2987
+ width: layerW,
2988
+ height: layerH,
2989
+ textureExtent: [layerW / texW, layerH / texH],
2990
+ rotation: [
2991
+ target?.rotateX ?? lop.rotateX ?? 0,
2992
+ target?.rotateY ?? lop.rotateY ?? 0,
2993
+ (target?.rotateZ ?? lop.rotateZ ?? 0) !== 0 ? target?.rotateZ ?? lop.rotateZ ?? 0 : rotation
2994
+ ],
2995
+ scale: [
2996
+ scale$1,
2997
+ scale$1,
2998
+ target?.scaleZ ?? lop.scaleZ ?? 1
2999
+ ],
3000
+ anchor: [lop.anchorX ?? .5, lop.anchorY ?? .5],
3001
+ borderRadius: br,
3002
+ borderWidth: lop.borderWidth,
3003
+ borderColor: lop.borderColor,
3004
+ opacity: layerOpacity,
3005
+ twoSided: target?.twoSided ?? lop.twoSided ?? true,
3006
+ material: target?.material ?? lop.material,
3007
+ shininess: target?.shininess ?? lop.shininess,
3008
+ roughness: target?.roughness ?? lop.roughness,
3009
+ specularIntensity: target?.specularIntensity ?? lop.specularIntensity,
3010
+ ambientIntensity: target?.ambientIntensity ?? lop.ambientIntensity,
3011
+ metallic: target?.metallic ?? lop.metallic,
3012
+ ...modelMatrix && { modelMatrix }
3013
+ };
3014
+ const quadHasContent = layerHasRaster || Boolean(quadOpts.borderWidth && quadOpts.borderWidth > 0 && quadOpts.borderColor);
3015
+ if (!glyphBatches || quadHasContent) pending3DItems.push({
3016
+ kind: "quad",
3017
+ texture: renderedTex,
3018
+ opts: quadOpts
3019
+ });
3020
+ if (glyphBatches && glyphBatches.length > 0) pending3DItems.push({
3021
+ kind: "slug",
3022
+ batches: glyphBatches,
3023
+ opts: quadOpts,
3024
+ stackBase: quadHasContent ? 1 : 0
3025
+ });
3026
+ return {
3027
+ finalTex: activeTex,
3028
+ finalView: activeView
3029
+ };
3030
+ }
3031
+ if (is3D && quad3D) {
3032
+ const renderedTex = finalTex;
3033
+ if (borderResult) {
3034
+ const borderPass = ctx.renderer.beginFrame(encoder, renderedTex.createView(), {
3035
+ r: 0,
3036
+ g: 0,
3037
+ b: 0,
3038
+ a: 0
3039
+ }, texW, texH, "load");
3040
+ ctx.renderer.drawTexture(borderPass, borderResult.borderTex, {
3041
+ x: borderResult.ox,
3042
+ y: borderResult.oy,
3043
+ width: borderResult.ow,
3044
+ height: borderResult.oh
3045
+ }, { opacity: 1 });
3046
+ borderPass.end();
3047
+ }
3048
+ if (blendMode === "normal" || blendMode === "source-over") drawProjectedQuad(ctx, encoder, activeView, renderedTex, quad3D, targetW, targetH, layerOpacity);
3049
+ else {
3050
+ const layerExcludes = [
3051
+ activeTex,
3052
+ targetTexture,
3053
+ renderedTex,
3054
+ ...excludes
3055
+ ];
3056
+ if (borderResult) layerExcludes.push(borderResult.borderTex);
3057
+ const layerTex = ctx.renderer.getTemporaryTexture(targetW, targetH, layerExcludes);
3058
+ const layerView = layerTex.createView();
3059
+ ctx.renderer.beginFrame(encoder, layerView, {
3060
+ r: 0,
3061
+ g: 0,
3062
+ b: 0,
3063
+ a: 0
3064
+ }, targetW, targetH, "clear").end();
3065
+ drawProjectedQuad(ctx, encoder, layerView, renderedTex, quad3D, targetW, targetH, layerOpacity);
3066
+ activeTex = ctx.renderer.composite(encoder, activeTex, layerTex, blendMode, [targetTexture, ...excludes]);
3067
+ activeView = activeTex.createView();
3068
+ }
3069
+ return {
3070
+ finalTex: activeTex,
3071
+ finalView: activeView
3072
+ };
3073
+ }
3074
+ if (blendMode === "normal" || blendMode === "source-over") {
3075
+ const drawPass = ctx.renderer.beginFrame(encoder, activeView, {
3076
+ r: 0,
3077
+ g: 0,
3078
+ b: 0,
3079
+ a: 0
3080
+ }, targetW, targetH, "load");
3081
+ ctx.renderer.pushTransform(layerMatrix);
3082
+ ctx.renderer.drawTexture(drawPass, finalTex, {
3083
+ x: -blurPadX,
3084
+ y: -blurPadY,
3085
+ width: layerW + blurPadX * 2,
3086
+ height: layerH + blurPadY * 2
3087
+ }, { opacity: layerOpacity });
3088
+ if (borderResult) ctx.renderer.drawTexture(drawPass, borderResult.borderTex, {
3089
+ x: borderResult.ox,
3090
+ y: borderResult.oy,
3091
+ width: borderResult.ow,
3092
+ height: borderResult.oh
3093
+ }, { opacity: layerOpacity });
3094
+ ctx.renderer.popTransform();
3095
+ drawPass.end();
3096
+ } else {
3097
+ const layerExcludes = [
3098
+ finalTex,
3099
+ activeTex,
3100
+ targetTexture,
3101
+ ...excludes
3102
+ ];
3103
+ if (borderResult) layerExcludes.push(borderResult.borderTex);
3104
+ const layerTex = ctx.renderer.getTemporaryTexture(targetW, targetH, layerExcludes);
3105
+ const layerView = layerTex.createView();
3106
+ ctx.renderer.beginFrame(encoder, layerView, {
3107
+ r: 0,
3108
+ g: 0,
3109
+ b: 0,
3110
+ a: 0
3111
+ }, targetW, targetH, "clear").end();
3112
+ const drawPass = ctx.renderer.beginFrame(encoder, layerView, {
3113
+ r: 0,
3114
+ g: 0,
3115
+ b: 0,
3116
+ a: 0
3117
+ }, targetW, targetH, "load");
3118
+ ctx.renderer.pushTransform(layerMatrix);
3119
+ ctx.renderer.drawTexture(drawPass, finalTex, {
3120
+ x: -blurPadX,
3121
+ y: -blurPadY,
3122
+ width: layerW + blurPadX * 2,
3123
+ height: layerH + blurPadY * 2
3124
+ }, { opacity: layerOpacity });
3125
+ if (borderResult) ctx.renderer.drawTexture(drawPass, borderResult.borderTex, {
3126
+ x: borderResult.ox,
3127
+ y: borderResult.oy,
3128
+ width: borderResult.ow,
3129
+ height: borderResult.oh
3130
+ }, { opacity: layerOpacity });
3131
+ ctx.renderer.popTransform();
3132
+ drawPass.end();
3133
+ activeTex = ctx.renderer.composite(encoder, activeTex, layerTex, blendMode, [targetTexture, ...excludes]);
3134
+ activeView = activeTex.createView();
3135
+ }
3136
+ return {
3137
+ finalTex: activeTex,
3138
+ finalView: activeView
3139
+ };
3140
+ };
3141
+ const initialExcludes = [
3142
+ compTex,
3143
+ targetTexture,
3144
+ ...props.excludeTextures || []
3145
+ ];
3146
+ currentCompTex = (await renderNodeTree(virtualMedia.children ?? [], compTex, compView, initialExcludes, 1, new DOMMatrix(), null, nativeWidth, nativeHeight, null, true)).finalTex;
3147
+ ctx.renderer.popTransform();
3148
+ ctx.renderer.popScissor();
3149
+ const finalPass = ctx.renderer.beginFrame(encoder, targetView, {
3150
+ r: 0,
3151
+ g: 0,
3152
+ b: 0,
3153
+ a: 0
3154
+ }, targetWidth, targetHeight, "load");
3155
+ ctx.renderer.drawTexture(finalPass, currentCompTex, {
3156
+ x: 0,
3157
+ y: 0,
3158
+ width: props.containerWidth ?? targetWidth,
3159
+ height: props.containerHeight ?? targetHeight
3160
+ }, { opacity });
3161
+ args.pass = finalPass;
3162
+ }
3163
+ };
3164
+ function createBorderTexture(ctx, encoder, w, h, br, borderWidth, borderColor, strokeAlign, excludeTextures) {
3165
+ if (w <= 0 || h <= 0 || !borderColor || !borderWidth || borderWidth <= 0) return null;
3166
+ let ox = 0;
3167
+ let oy = 0;
3168
+ let ow = w;
3169
+ let oh = h;
3170
+ let outerRadius = br;
3171
+ let innerRadius = br;
3172
+ if (strokeAlign === "inside") {
3173
+ ox = 0;
3174
+ oy = 0;
3175
+ ow = w;
3176
+ oh = h;
3177
+ outerRadius = br;
3178
+ innerRadius = Math.max(0, br - borderWidth);
3179
+ } else if (strokeAlign === "outside") {
3180
+ ox = -borderWidth;
3181
+ oy = -borderWidth;
3182
+ ow = w + 2 * borderWidth;
3183
+ oh = h + 2 * borderWidth;
3184
+ outerRadius = br + borderWidth;
3185
+ innerRadius = br;
3186
+ } else {
3187
+ ox = -borderWidth / 2;
3188
+ oy = -borderWidth / 2;
3189
+ ow = w + borderWidth;
3190
+ oh = h + borderWidth;
3191
+ outerRadius = br + borderWidth / 2;
3192
+ innerRadius = Math.max(0, br - borderWidth / 2);
3193
+ }
3194
+ if (br <= 0) {
3195
+ outerRadius = 0;
3196
+ innerRadius = 0;
3197
+ }
3198
+ if (ow <= 0 || oh <= 0) return null;
3199
+ const borderTexW = Math.ceil(ow);
3200
+ const borderTexH = Math.ceil(oh);
3201
+ const outerTex = ctx.renderer.getTemporaryTexture(borderTexW, borderTexH, excludeTextures);
3202
+ const outerView = outerTex.createView();
3203
+ ctx.renderer.beginFrame(encoder, outerView, {
3204
+ r: 0,
3205
+ g: 0,
3206
+ b: 0,
3207
+ a: 0
3208
+ }, borderTexW, borderTexH, "clear").end();
3209
+ const outerDrawPass = ctx.renderer.beginFrame(encoder, outerView, {
3210
+ r: 0,
3211
+ g: 0,
3212
+ b: 0,
3213
+ a: 0
3214
+ }, borderTexW, borderTexH, "load");
3215
+ ctx.renderer.drawRect(outerDrawPass, {
3216
+ x: 0,
3217
+ y: 0,
3218
+ width: ow,
3219
+ height: oh
3220
+ }, borderColor, outerRadius);
3221
+ outerDrawPass.end();
3222
+ const innerW = ow - 2 * borderWidth;
3223
+ const innerH = oh - 2 * borderWidth;
3224
+ if (innerW > 0 && innerH > 0) {
3225
+ const innerTex = ctx.renderer.getTemporaryTexture(borderTexW, borderTexH, [outerTex, ...excludeTextures]);
3226
+ const innerView = innerTex.createView();
3227
+ ctx.renderer.beginFrame(encoder, innerView, {
3228
+ r: 0,
3229
+ g: 0,
3230
+ b: 0,
3231
+ a: 0
3232
+ }, borderTexW, borderTexH, "clear").end();
3233
+ const innerDrawPass = ctx.renderer.beginFrame(encoder, innerView, {
3234
+ r: 0,
3235
+ g: 0,
3236
+ b: 0,
3237
+ a: 0
3238
+ }, borderTexW, borderTexH, "load");
3239
+ ctx.renderer.drawRect(innerDrawPass, {
3240
+ x: borderWidth,
3241
+ y: borderWidth,
3242
+ width: innerW,
3243
+ height: innerH
3244
+ }, {
3245
+ r: 1,
3246
+ g: 1,
3247
+ b: 1,
3248
+ a: 1
3249
+ }, innerRadius);
3250
+ innerDrawPass.end();
3251
+ return {
3252
+ borderTex: ctx.renderer.composite(encoder, outerTex, innerTex, "mask-out", excludeTextures),
3253
+ ox,
3254
+ oy,
3255
+ ow,
3256
+ oh
3257
+ };
3258
+ }
3259
+ return {
3260
+ borderTex: outerTex,
3261
+ ox,
3262
+ oy,
3263
+ ow,
3264
+ oh
3265
+ };
3266
+ }
3267
+
3268
+ //#endregion
3269
+ //#region src/renderers/audio-processor.ts
3270
+ const compositorLayerAudioProcessor = async (channels, sampleRate, virtualMedia, ctx) => {
3271
+ const op = virtualMedia.operation;
3272
+ if (!op) return;
3273
+ const numChannels = channels.length;
3274
+ if (numChannels === 0) return;
3275
+ const numSamples = channels[0].length;
3276
+ if (numSamples === 0) return;
3277
+ if (op.op === "Compositor") {
3278
+ const master = typeof op.volume === "number" ? op.volume : 1;
3279
+ if (master === 1) return;
3280
+ for (let c = 0; c < numChannels; c++) {
3281
+ const ch = channels[c];
3282
+ for (let i = 0; i < ch.length; i++) ch[i] *= master;
3283
+ }
3284
+ return;
3285
+ }
3286
+ const volume = typeof op.volume === "number" ? op.volume : 1;
3287
+ const muted = !!op.muted;
3288
+ const animation = op.animation;
3289
+ const fps = ctx?.fps ?? 24;
3290
+ const tracks = animation?.tracks || [];
3291
+ const hasVolumeKeys = tracks.some((t) => t.prop === "volume" && t.keyframes && t.keyframes.length > 0);
3292
+ const hasMutedKeys = tracks.some((t) => t.prop === "muted" && t.keyframes && t.keyframes.length > 0);
3293
+ if (!hasVolumeKeys && !hasMutedKeys) return;
3294
+ const { tl, stub } = compileLayerTimeline(op.id || op.inputHandleId || "layer", animation, volume, muted, fps);
3295
+ const startTimeSec = (ctx?.elapsedMs ?? 0) / 1e3;
3296
+ const subChunkSize = 128;
3297
+ tl.seek(startTimeSec);
3298
+ let lastVol = stub.volume;
3299
+ let lastMuted = stub.muted;
3300
+ const baseGain = muted ? 0 : volume;
3301
+ for (let i = 0; i < numSamples; i += subChunkSize) {
3302
+ const nextI = Math.min(numSamples, i + subChunkSize);
3303
+ const tNext = startTimeSec + (nextI - i) / sampleRate;
3304
+ tl.seek(tNext);
3305
+ const nextVol = stub.volume;
3306
+ const nextMuted = stub.muted;
3307
+ for (let j = i; j < nextI; j++) {
3308
+ const p = (j - i) / (nextI - i);
3309
+ const currentVol = Math.min(1, Math.max(0, lastVol + (nextVol - lastVol) * p));
3310
+ const finalGain = (j - i < (nextI - i) / 2 ? lastMuted : nextMuted) ? 0 : currentVol;
3311
+ const relativeGain = baseGain === 0 ? 0 : finalGain / baseGain;
3312
+ if (relativeGain !== 1) for (let c = 0; c < numChannels; c++) channels[c][j] *= relativeGain;
3313
+ }
3314
+ lastVol = nextVol;
3315
+ lastMuted = nextMuted;
3316
+ }
3317
+ };
3318
+ audioRegistry.register("CompositorLayer", compositorLayerAudioProcessor);
3319
+
3320
+ //#endregion
3321
+ //#region src/renderers/index.ts
3322
+ var renderers_default = defineRenderer({ WebGPURenderer: CompositorWebGPURenderer });
3323
+
3324
+ //#endregion
3325
+ export { compileLayerTimeline as a, mappedEase as c, solveHomography as i, setCachedTimeline as l, Transform3DMath as n, compileTimeline as o, has3DTransform as r, getCachedTimeline as s, renderers_default as t };
3326
+ //# sourceMappingURL=renderers-DoYYCzgA.mjs.map