@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.
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +21 -0
- package/CHANGELOG.md +32 -0
- package/LICENCE +202 -0
- package/SKILL.md +370 -0
- package/dist/index-B0ir9Z7o.d.mts +139 -0
- package/dist/index-B0ir9Z7o.d.mts.map +1 -0
- package/dist/index.d.mts +230 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +674 -0
- package/dist/index.mjs.map +1 -0
- package/dist/renderer.d.mts +2 -0
- package/dist/renderer.mjs +3 -0
- package/dist/renderers-DoYYCzgA.mjs +3326 -0
- package/dist/renderers-DoYYCzgA.mjs.map +1 -0
- package/package.json +51 -3
- package/src/index.ts +2 -0
- package/src/renderers/audio-processor.ts +123 -0
- package/src/renderers/container-bg.test.ts +22 -0
- package/src/renderers/container-bg.ts +15 -0
- package/src/renderers/index.ts +9 -0
- package/src/renderers/transform.test.ts +120 -0
- package/src/renderers/transform.ts +101 -0
- package/src/renderers/transform3d.test.ts +290 -0
- package/src/renderers/transform3d.ts +428 -0
- package/src/renderers/webgpu-renderer.ts +4038 -0
- package/src/shared/audio-reactive.test.ts +224 -0
- package/src/shared/bezier-solver.test.ts +92 -0
- package/src/shared/bezier-solver.ts +257 -0
- package/src/shared/compiler.test.ts +1309 -0
- package/src/shared/compiler.ts +996 -0
- package/src/shared/config.ts +44 -0
- package/src/shared/index.ts +3 -0
- package/src/shared/kinetic-typography.test.ts +138 -0
- package/src/shared/presets.test.ts +217 -0
- package/src/shared/presets.ts +644 -0
- package/src/shared/processor.ts +105 -0
- package/tsconfig.json +13 -0
- package/tsdown.config.ts +13 -0
- 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
|