@pixerate/flair 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +65 -14
- package/dist/catalog/index.d.ts +5 -4
- package/dist/catalog/index.js +12 -4
- package/dist/{chunk-JWZCPMFG.js → chunk-D7NLHC7K.js} +117 -674
- package/dist/chunk-D7NLHC7K.js.map +1 -0
- package/dist/{chunk-3FAUZOGL.js → chunk-FIJOMGPG.js} +2 -2
- package/dist/{chunk-UQQNZSYL.js → chunk-GTIAOYOK.js} +211 -7
- package/dist/chunk-GTIAOYOK.js.map +1 -0
- package/dist/{chunk-MWVTQFV5.js → chunk-IWKM5FJK.js} +410 -59
- package/dist/chunk-IWKM5FJK.js.map +1 -0
- package/dist/{chunk-HKXOHRCD.js → chunk-VA74HBPK.js} +2 -2
- package/dist/chunk-WAMA45G3.js +998 -0
- package/dist/chunk-WAMA45G3.js.map +1 -0
- package/dist/{chunk-J55UBNAT.js → chunk-WC3VADZC.js} +42 -36
- package/dist/chunk-WC3VADZC.js.map +1 -0
- package/dist/cli.js +12 -11
- package/dist/cli.js.map +1 -1
- package/dist/{controls-DdXMbovi.d.ts → controls-Syr6drzO.d.ts} +4 -3
- package/dist/editor/index.d.ts +101 -49
- package/dist/editor/index.js +198 -99
- package/dist/editor/index.js.map +1 -1
- package/dist/{export-FEBPEEO7.js → export-DH2HMACO.js} +4 -3
- package/dist/index.d.ts +193 -17
- package/dist/index.js +69 -33
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/dist/recorder-KEX3U35D.js +10 -0
- package/dist/{renderer-Oyynz-fs.d.ts → renderer-mzvhKzKa.d.ts} +1 -1
- package/dist/types-CsFUZZIv.d.ts +102 -0
- package/package.json +3 -3
- package/dist/chunk-36DTNSHU.js +0 -160
- package/dist/chunk-36DTNSHU.js.map +0 -1
- package/dist/chunk-J55UBNAT.js.map +0 -1
- package/dist/chunk-JWZCPMFG.js.map +0 -1
- package/dist/chunk-MWVTQFV5.js.map +0 -1
- package/dist/chunk-UQQNZSYL.js.map +0 -1
- package/dist/recorder-WJTWMEQN.js +0 -10
- package/dist/types-CkKkavvB.d.ts +0 -71
- /package/dist/{chunk-3FAUZOGL.js.map → chunk-FIJOMGPG.js.map} +0 -0
- /package/dist/{chunk-HKXOHRCD.js.map → chunk-VA74HBPK.js.map} +0 -0
- /package/dist/{export-FEBPEEO7.js.map → export-DH2HMACO.js.map} +0 -0
- /package/dist/{recorder-WJTWMEQN.js.map → recorder-KEX3U35D.js.map} +0 -0
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ManualClock
|
|
3
|
+
} from "./chunk-WMZQCUFJ.js";
|
|
4
|
+
|
|
5
|
+
// src/time/noise.ts
|
|
6
|
+
var GOLDEN_GAMMA = 0x9e3779b97f4a7c15n;
|
|
7
|
+
var MULT1 = 0xbf58476d1ce4e5b9n;
|
|
8
|
+
var MULT2 = 0x94d049bb133111ebn;
|
|
9
|
+
var MASK64 = 0xffffffffffffffffn;
|
|
10
|
+
var BASE_MIX = 0x2545f4914f6cdd1dn;
|
|
11
|
+
var POW2_53 = 9007199254740992;
|
|
12
|
+
function hash64(x) {
|
|
13
|
+
let v = x + GOLDEN_GAMMA & MASK64;
|
|
14
|
+
v = (v ^ v >> 30n) * MULT1 & MASK64;
|
|
15
|
+
v = (v ^ v >> 27n) * MULT2 & MASK64;
|
|
16
|
+
return (v ^ v >> 31n) & MASK64;
|
|
17
|
+
}
|
|
18
|
+
function mixSeeds(parts) {
|
|
19
|
+
let h = BASE_MIX;
|
|
20
|
+
for (const p of parts) {
|
|
21
|
+
const val = typeof p === "bigint" ? p : BigInt(Math.floor(p));
|
|
22
|
+
h = hash64(h ^ hash64(val & MASK64));
|
|
23
|
+
}
|
|
24
|
+
return h;
|
|
25
|
+
}
|
|
26
|
+
function unitFloat(seed) {
|
|
27
|
+
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
28
|
+
const hashed = hash64(s);
|
|
29
|
+
const shifted = Number(hashed >> 11n);
|
|
30
|
+
return shifted / POW2_53;
|
|
31
|
+
}
|
|
32
|
+
function quinticFade(t) {
|
|
33
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
34
|
+
}
|
|
35
|
+
function lattice(seed, i) {
|
|
36
|
+
const mixed = seed ^ hash64(i & MASK64);
|
|
37
|
+
return unitFloat(mixed) * 2 - 1;
|
|
38
|
+
}
|
|
39
|
+
function noise1d(seed, x) {
|
|
40
|
+
if (!Number.isFinite(x)) return 0;
|
|
41
|
+
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
42
|
+
const floorX = Math.floor(x);
|
|
43
|
+
const u = quinticFade(x - floorX);
|
|
44
|
+
const i = BigInt(Math.floor(floorX));
|
|
45
|
+
const a = lattice(s, i);
|
|
46
|
+
const b = lattice(s, i + 1n);
|
|
47
|
+
return a + (b - a) * u;
|
|
48
|
+
}
|
|
49
|
+
function noise3d(x, y, z, seed = 0) {
|
|
50
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
54
|
+
const fx = Math.floor(x);
|
|
55
|
+
const fy = Math.floor(y);
|
|
56
|
+
const fz = Math.floor(z);
|
|
57
|
+
const u = quinticFade(x - fx);
|
|
58
|
+
const v = quinticFade(y - fy);
|
|
59
|
+
const w = quinticFade(z - fz);
|
|
60
|
+
const ix = BigInt(fx);
|
|
61
|
+
const iy = BigInt(fy);
|
|
62
|
+
const iz = BigInt(fz);
|
|
63
|
+
const g = (dx, dy, dz) => {
|
|
64
|
+
const combined = mixSeeds([s, ix + dx, iy + dy, iz + dz]);
|
|
65
|
+
return unitFloat(combined) * 2 - 1;
|
|
66
|
+
};
|
|
67
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
68
|
+
const x00 = lerp(g(0n, 0n, 0n), g(1n, 0n, 0n), u);
|
|
69
|
+
const x10 = lerp(g(0n, 1n, 0n), g(1n, 1n, 0n), u);
|
|
70
|
+
const x01 = lerp(g(0n, 0n, 1n), g(1n, 0n, 1n), u);
|
|
71
|
+
const x11 = lerp(g(0n, 1n, 1n), g(1n, 1n, 1n), u);
|
|
72
|
+
const y0 = lerp(x00, x10, v);
|
|
73
|
+
const y1 = lerp(x01, x11, v);
|
|
74
|
+
return lerp(y0, y1, w);
|
|
75
|
+
}
|
|
76
|
+
function fractalWiggle(freq, amp, octaves = 3, ampMult = 0.5, t = 0, seed = 42, dimension = 0) {
|
|
77
|
+
const oct = Math.max(1, Math.min(16, Math.floor(octaves)));
|
|
78
|
+
let currentAmp = amp;
|
|
79
|
+
let currentFreq = freq;
|
|
80
|
+
let sum = 0;
|
|
81
|
+
const baseSeed = BigInt(seed);
|
|
82
|
+
for (let k = 0; k < oct; k++) {
|
|
83
|
+
const s = mixSeeds([baseSeed, BigInt(dimension), BigInt(k)]);
|
|
84
|
+
const phaseOffset = unitFloat(s) * 1e3;
|
|
85
|
+
sum += currentAmp * noise1d(s, currentFreq * t + phaseOffset);
|
|
86
|
+
currentAmp *= ampMult;
|
|
87
|
+
currentFreq *= 2;
|
|
88
|
+
}
|
|
89
|
+
return sum;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/scene/types.ts
|
|
93
|
+
function isElementCommand(cmd) {
|
|
94
|
+
return cmd.type === "element" && "element" in cmd && !!cmd.element;
|
|
95
|
+
}
|
|
96
|
+
function isUpdateCommand(cmd) {
|
|
97
|
+
return cmd.type === "update" && "id" in cmd && typeof cmd.id === "string";
|
|
98
|
+
}
|
|
99
|
+
function isDeleteCommand(cmd) {
|
|
100
|
+
return cmd.type === "delete" && "id" in cmd && typeof cmd.id === "string";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/scene/geometry.ts
|
|
104
|
+
function boundsOf(e) {
|
|
105
|
+
if (e.points?.length) {
|
|
106
|
+
const xs = e.points.map((p) => e.x + p[0]);
|
|
107
|
+
const ys = e.points.map((p) => e.y + p[1]);
|
|
108
|
+
const x = Math.min(...xs);
|
|
109
|
+
const y = Math.min(...ys);
|
|
110
|
+
return {
|
|
111
|
+
x,
|
|
112
|
+
y,
|
|
113
|
+
width: Math.max(1, Math.max(...xs) - x),
|
|
114
|
+
height: Math.max(1, Math.max(...ys) - y)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { x: e.x, y: e.y, width: Math.max(1, e.width), height: Math.max(1, e.height) };
|
|
118
|
+
}
|
|
119
|
+
function overlapArea(a, b) {
|
|
120
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
121
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
122
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
123
|
+
}
|
|
124
|
+
var areaOf = (r) => r.width * r.height;
|
|
125
|
+
|
|
126
|
+
// src/scene/camera.ts
|
|
127
|
+
var DEFAULT_STAGE = {
|
|
128
|
+
width: 1920,
|
|
129
|
+
height: 1080,
|
|
130
|
+
caption: 0
|
|
131
|
+
};
|
|
132
|
+
var CAMERA_MS = 600;
|
|
133
|
+
var DEFAULT_PERSPECTIVE = 1.2;
|
|
134
|
+
var MAX_TILT = 75;
|
|
135
|
+
var boardHeight = (stage) => stage.height - (stage.caption ?? 0);
|
|
136
|
+
function viewAt(cx, cy, zoom, stage) {
|
|
137
|
+
return {
|
|
138
|
+
zoom,
|
|
139
|
+
scrollX: stage.width / (2 * zoom) - cx,
|
|
140
|
+
scrollY: boardHeight(stage) / (2 * zoom) - cy
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function centreOf(view, stage) {
|
|
144
|
+
return [
|
|
145
|
+
stage.width / (2 * view.zoom) - view.scrollX,
|
|
146
|
+
boardHeight(stage) / (2 * view.zoom) - view.scrollY
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
function frame(rect, stage) {
|
|
150
|
+
if (!(rect.width > 0 && rect.height > 0)) {
|
|
151
|
+
throw new Error("A camera rectangle needs a positive width and height");
|
|
152
|
+
}
|
|
153
|
+
const zoom = Math.min(stage.width / rect.width, boardHeight(stage) / rect.height);
|
|
154
|
+
return viewAt(rect.x + rect.width / 2, rect.y + rect.height / 2, zoom, stage);
|
|
155
|
+
}
|
|
156
|
+
function calculateFramingRect(elements, stage, options = {}) {
|
|
157
|
+
const activeElements = elements.filter((e) => !e.isDeleted);
|
|
158
|
+
const targetH = boardHeight(stage);
|
|
159
|
+
const stageAspect = stage.width / Math.max(1, targetH);
|
|
160
|
+
if (activeElements.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
x: 0,
|
|
163
|
+
y: 0,
|
|
164
|
+
width: stage.width,
|
|
165
|
+
height: targetH
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
let minX = Infinity;
|
|
169
|
+
let minY = Infinity;
|
|
170
|
+
let maxX = -Infinity;
|
|
171
|
+
let maxY = -Infinity;
|
|
172
|
+
for (const el of activeElements) {
|
|
173
|
+
const b = boundsOf(el);
|
|
174
|
+
if (b.x < minX) minX = b.x;
|
|
175
|
+
if (b.y < minY) minY = b.y;
|
|
176
|
+
if (b.x + b.width > maxX) maxX = b.x + b.width;
|
|
177
|
+
if (b.y + b.height > maxY) maxY = b.y + b.height;
|
|
178
|
+
}
|
|
179
|
+
let width = maxX - minX;
|
|
180
|
+
let height = maxY - minY;
|
|
181
|
+
const padding = options.padding ?? 0.15;
|
|
182
|
+
if (padding > 0) {
|
|
183
|
+
const padX = width * padding;
|
|
184
|
+
const padY = height * padding;
|
|
185
|
+
minX -= padX;
|
|
186
|
+
minY -= padY;
|
|
187
|
+
width += padX * 2;
|
|
188
|
+
height += padY * 2;
|
|
189
|
+
}
|
|
190
|
+
if (options.minWidth && width < options.minWidth) {
|
|
191
|
+
const diff = options.minWidth - width;
|
|
192
|
+
minX -= diff / 2;
|
|
193
|
+
width = options.minWidth;
|
|
194
|
+
}
|
|
195
|
+
if (options.minHeight && height < options.minHeight) {
|
|
196
|
+
const diff = options.minHeight - height;
|
|
197
|
+
minY -= diff / 2;
|
|
198
|
+
height = options.minHeight;
|
|
199
|
+
}
|
|
200
|
+
const currentAspect = width / Math.max(1, height);
|
|
201
|
+
if (currentAspect < stageAspect) {
|
|
202
|
+
const targetWidth = height * stageAspect;
|
|
203
|
+
const diff = targetWidth - width;
|
|
204
|
+
minX -= diff / 2;
|
|
205
|
+
width = targetWidth;
|
|
206
|
+
} else if (currentAspect > stageAspect) {
|
|
207
|
+
const targetHeight = width / stageAspect;
|
|
208
|
+
const diff = targetHeight - height;
|
|
209
|
+
minY -= diff / 2;
|
|
210
|
+
height = targetHeight;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
x: minX,
|
|
214
|
+
y: minY,
|
|
215
|
+
width,
|
|
216
|
+
height
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
var ANGLES = ["tilt", "turn", "roll"];
|
|
220
|
+
function isAngled(view) {
|
|
221
|
+
return ANGLES.some((k) => (view[k] ?? 0) !== 0);
|
|
222
|
+
}
|
|
223
|
+
function cameraAngle(view) {
|
|
224
|
+
const out = {};
|
|
225
|
+
const clamp = (v, max) => Math.max(-max, Math.min(max, v));
|
|
226
|
+
if (view.tilt) out.tilt = clamp(view.tilt, MAX_TILT);
|
|
227
|
+
if (view.turn) out.turn = clamp(view.turn, MAX_TILT);
|
|
228
|
+
if (view.roll) out.roll = clamp(view.roll, 180);
|
|
229
|
+
if (view.perspective !== void 0 && view.perspective !== DEFAULT_PERSPECTIVE) out.perspective = Math.max(0.3, view.perspective);
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
function between(from, to, t, stage) {
|
|
233
|
+
const [x0, y0] = centreOf(from, stage);
|
|
234
|
+
const [x1, y1] = centreOf(to, stage);
|
|
235
|
+
const zoom = from.zoom * Math.pow(to.zoom / from.zoom, t);
|
|
236
|
+
const view = viewAt(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t, zoom, stage);
|
|
237
|
+
if (!isAngled(from) && !isAngled(to) && from.perspective === to.perspective) return view;
|
|
238
|
+
const lerp = (a = 0, b = 0) => a + (b - a) * t;
|
|
239
|
+
return {
|
|
240
|
+
...view,
|
|
241
|
+
...cameraAngle({
|
|
242
|
+
tilt: lerp(from.tilt, to.tilt),
|
|
243
|
+
turn: lerp(from.turn, to.turn),
|
|
244
|
+
roll: lerp(from.roll, to.roll),
|
|
245
|
+
perspective: lerp(from.perspective ?? DEFAULT_PERSPECTIVE, to.perspective ?? DEFAULT_PERSPECTIVE)
|
|
246
|
+
})
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function cameraTransform(view, stage, width = stage.width) {
|
|
250
|
+
if (!isAngled(view)) return "none";
|
|
251
|
+
const a = cameraAngle(view);
|
|
252
|
+
const distance = (a.perspective ?? DEFAULT_PERSPECTIVE) * width;
|
|
253
|
+
return `perspective(${round(distance)}px) rotateX(${round(a.tilt ?? 0)}deg) rotateY(${round(a.turn ?? 0)}deg) rotateZ(${round(a.roll ?? 0)}deg)`;
|
|
254
|
+
}
|
|
255
|
+
function cameraBlur(view, stage, width = stage.width) {
|
|
256
|
+
const blur = (view.blur ?? 0) * (width / stage.width);
|
|
257
|
+
if (!(blur >= 0.05)) return [0, 0];
|
|
258
|
+
if (view.blurAngle === void 0) return [round(blur), round(blur)];
|
|
259
|
+
const a = (view.blurAngle - (cameraAngle(view).roll ?? 0)) * Math.PI / 180;
|
|
260
|
+
const across = blur * 0.15;
|
|
261
|
+
return [round(Math.max(across, blur * Math.abs(Math.cos(a)))), round(Math.max(across, blur * Math.abs(Math.sin(a))))];
|
|
262
|
+
}
|
|
263
|
+
function cameraFilter(view, stage, width = stage.width) {
|
|
264
|
+
const blur = (view.blur ?? 0) * (width / stage.width);
|
|
265
|
+
return blur >= 0.05 ? `blur(${round(blur)}px)` : "none";
|
|
266
|
+
}
|
|
267
|
+
var round = (n) => Math.round(n * 1e3) / 1e3;
|
|
268
|
+
function projectPoint(view, stage, [x, y]) {
|
|
269
|
+
const cx = stage.width / 2;
|
|
270
|
+
const cy = boardHeight(stage) / 2;
|
|
271
|
+
let px = (x + view.scrollX) * view.zoom - cx;
|
|
272
|
+
let py = (y + view.scrollY) * view.zoom - cy;
|
|
273
|
+
if (!isAngled(view)) return [px + cx, py + cy];
|
|
274
|
+
const a = cameraAngle(view);
|
|
275
|
+
const rad = Math.PI / 180;
|
|
276
|
+
const [sr, cr] = [Math.sin((a.roll ?? 0) * rad), Math.cos((a.roll ?? 0) * rad)];
|
|
277
|
+
[px, py] = [px * cr - py * sr, px * sr + py * cr];
|
|
278
|
+
const [st, ct] = [Math.sin((a.turn ?? 0) * rad), Math.cos((a.turn ?? 0) * rad)];
|
|
279
|
+
let pz = -px * st;
|
|
280
|
+
px = px * ct;
|
|
281
|
+
const [sx, cx2] = [Math.sin((a.tilt ?? 0) * rad), Math.cos((a.tilt ?? 0) * rad)];
|
|
282
|
+
[py, pz] = [py * cx2 - pz * sx, py * sx + pz * cx2];
|
|
283
|
+
const distance = (a.perspective ?? DEFAULT_PERSPECTIVE) * stage.width;
|
|
284
|
+
if (pz >= distance) return null;
|
|
285
|
+
const k = distance / (distance - pz);
|
|
286
|
+
return [cx + px * k, cy + py * k];
|
|
287
|
+
}
|
|
288
|
+
function screenMotion(from, to, stage) {
|
|
289
|
+
const [x0, y0] = centreOf(from, stage);
|
|
290
|
+
const [x1, y1] = centreOf(to, stage);
|
|
291
|
+
const zoom = Math.sqrt(from.zoom * to.zoom);
|
|
292
|
+
const scale = Math.abs(Math.log(to.zoom / from.zoom)) * stage.width * 0.5;
|
|
293
|
+
const turn = ANGLES.reduce((sum, k) => sum + Math.abs((to[k] ?? 0) - (from[k] ?? 0)), 0) * (stage.width / 180);
|
|
294
|
+
return { dx: (x1 - x0) * zoom, dy: (y1 - y0) * zoom, other: scale + turn };
|
|
295
|
+
}
|
|
296
|
+
function motionBlur(from, to, stage, strength) {
|
|
297
|
+
if (!(strength > 0)) return 0;
|
|
298
|
+
const { dx, dy, other } = screenMotion(from, to, stage);
|
|
299
|
+
return round(Math.min(40, (Math.hypot(dx, dy) + other) * 0.35 * strength));
|
|
300
|
+
}
|
|
301
|
+
function motionBlurAngle(from, to, stage) {
|
|
302
|
+
const { dx, dy, other } = screenMotion(from, to, stage);
|
|
303
|
+
const pan = Math.hypot(dx, dy);
|
|
304
|
+
if (!(pan > 0) || pan < 0.6 * (pan + other)) return void 0;
|
|
305
|
+
const a = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
306
|
+
return round((a % 180 + 180) % 180);
|
|
307
|
+
}
|
|
308
|
+
function moveBlur(from, to, stage, strength) {
|
|
309
|
+
const blur = motionBlur(from, to, stage, strength);
|
|
310
|
+
if (!(blur > 0)) return {};
|
|
311
|
+
const blurAngle = motionBlurAngle(from, to, stage);
|
|
312
|
+
return blurAngle === void 0 ? { blur } : { blur, blurAngle };
|
|
313
|
+
}
|
|
314
|
+
function visibleRect(view, stage) {
|
|
315
|
+
return {
|
|
316
|
+
x: -view.scrollX,
|
|
317
|
+
y: -view.scrollY,
|
|
318
|
+
width: stage.width / view.zoom,
|
|
319
|
+
height: boardHeight(stage) / view.zoom
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
323
|
+
|
|
324
|
+
// src/scene/check.ts
|
|
325
|
+
var NO_CHECK = { framing: [], overlaps: [], edges: [], underTitle: [] };
|
|
326
|
+
var SMALL_TEXT_PX = 24;
|
|
327
|
+
function titleBox(title, stage) {
|
|
328
|
+
const width = Math.min(stage.width, title.length * 20 + 64);
|
|
329
|
+
return { x: (stage.width - width) / 2, y: 24, width, height: 60 };
|
|
330
|
+
}
|
|
331
|
+
function touchedIds(shot) {
|
|
332
|
+
const ids = /* @__PURE__ */ new Set();
|
|
333
|
+
for (const cmd of shot.run ?? []) {
|
|
334
|
+
if (isElementCommand(cmd)) {
|
|
335
|
+
ids.add(cmd.element.id);
|
|
336
|
+
} else if ("id" in cmd && typeof cmd.id === "string") {
|
|
337
|
+
ids.add(cmd.id);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return [...ids];
|
|
341
|
+
}
|
|
342
|
+
var IN_FRAME = 0.95;
|
|
343
|
+
function screenBox(box, view, stage) {
|
|
344
|
+
const corners = [
|
|
345
|
+
[box.x, box.y],
|
|
346
|
+
[box.x + box.width, box.y],
|
|
347
|
+
[box.x, box.y + box.height],
|
|
348
|
+
[box.x + box.width, box.y + box.height]
|
|
349
|
+
].map(([x2, y2]) => projectPoint(view, stage, [x2, y2]));
|
|
350
|
+
if (corners.some((c) => !c)) return null;
|
|
351
|
+
const xs = corners.map((c) => c[0]);
|
|
352
|
+
const ys = corners.map((c) => c[1]);
|
|
353
|
+
const x = Math.min(...xs);
|
|
354
|
+
const y = Math.min(...ys);
|
|
355
|
+
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
356
|
+
}
|
|
357
|
+
function checkShot(shot, elements, view, stage, title) {
|
|
358
|
+
const screen = { x: 0, y: 0, width: stage.width, height: boardHeight(stage) };
|
|
359
|
+
const touched = new Set(touchedIds(shot));
|
|
360
|
+
const framing = [];
|
|
361
|
+
const edges = [];
|
|
362
|
+
const overlaps = [];
|
|
363
|
+
const activeElements = elements.filter((e) => !e.isDeleted && (e.opacity === void 0 || e.opacity > 0));
|
|
364
|
+
for (const el of activeElements) {
|
|
365
|
+
const box = screenBox(boundsOf(el), view, stage);
|
|
366
|
+
const overlap = box ? overlapArea(box, screen) : 0;
|
|
367
|
+
const boxArea = box ? areaOf(box) : 0;
|
|
368
|
+
if (touched.has(el.id)) {
|
|
369
|
+
if (overlap === 0) {
|
|
370
|
+
framing.push({ id: el.id, how: "outside" });
|
|
371
|
+
} else if (boxArea > 0 && overlap / boxArea < IN_FRAME) {
|
|
372
|
+
framing.push({ id: el.id, how: "cut" });
|
|
373
|
+
}
|
|
374
|
+
} else if (overlap > 0 && boxArea > 0 && overlap / boxArea < IN_FRAME) {
|
|
375
|
+
edges.push(el.id);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const touchedList = activeElements.filter((e) => touched.has(e.id));
|
|
379
|
+
for (let i = 0; i < touchedList.length; i++) {
|
|
380
|
+
for (let j = i + 1; j < touchedList.length; j++) {
|
|
381
|
+
const a = touchedList[i];
|
|
382
|
+
const b = touchedList[j];
|
|
383
|
+
const aBox = boundsOf(a);
|
|
384
|
+
const bBox = boundsOf(b);
|
|
385
|
+
const ol = overlapArea(aBox, bBox);
|
|
386
|
+
const minArea = Math.min(areaOf(aBox), areaOf(bBox));
|
|
387
|
+
if (minArea > 0 && ol / minArea > 0.3) {
|
|
388
|
+
overlaps.push([a.id, b.id]);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const smallTexts = [];
|
|
393
|
+
let smallestText;
|
|
394
|
+
for (const el of activeElements) {
|
|
395
|
+
if (el.text && el.fontSize) {
|
|
396
|
+
const b = boundsOf(el);
|
|
397
|
+
const top = projectPoint(view, stage, [b.x + b.width / 2, b.y + b.height / 2]);
|
|
398
|
+
const bottom = projectPoint(view, stage, [b.x + b.width / 2, b.y + b.height / 2 + el.fontSize]);
|
|
399
|
+
if (!top || !bottom) continue;
|
|
400
|
+
const px = Math.round(Math.hypot(bottom[0] - top[0], bottom[1] - top[1]));
|
|
401
|
+
const item = { id: el.id, text: el.text, px };
|
|
402
|
+
if (!smallestText || px < smallestText.px) {
|
|
403
|
+
smallestText = item;
|
|
404
|
+
}
|
|
405
|
+
if (px < SMALL_TEXT_PX) {
|
|
406
|
+
smallTexts.push(item);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const underTitle = [];
|
|
411
|
+
if (title) {
|
|
412
|
+
const tBox = titleBox(title, stage);
|
|
413
|
+
for (const el of activeElements) {
|
|
414
|
+
const box = screenBox(boundsOf(el), view, stage);
|
|
415
|
+
if (box && overlapArea(box, tBox) > 0) {
|
|
416
|
+
underTitle.push(el.id);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
framing,
|
|
422
|
+
overlaps,
|
|
423
|
+
smallestText,
|
|
424
|
+
smallTexts: smallTexts.length ? smallTexts : void 0,
|
|
425
|
+
edges,
|
|
426
|
+
underTitle
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/scene/effects.ts
|
|
431
|
+
var EFFECT_TYPES = [
|
|
432
|
+
"gradient",
|
|
433
|
+
"flow",
|
|
434
|
+
"glow",
|
|
435
|
+
"spotlight",
|
|
436
|
+
"shimmer",
|
|
437
|
+
"ripple",
|
|
438
|
+
"timer",
|
|
439
|
+
"counter",
|
|
440
|
+
"highlight",
|
|
441
|
+
"blur",
|
|
442
|
+
"burst",
|
|
443
|
+
"media"
|
|
444
|
+
];
|
|
445
|
+
var EFFECT_FADE_MS = 300;
|
|
446
|
+
function effectGone(running, now) {
|
|
447
|
+
if (running.until !== void 0) {
|
|
448
|
+
return now >= running.until + EFFECT_FADE_MS;
|
|
449
|
+
}
|
|
450
|
+
if (running.effect.duration !== void 0) {
|
|
451
|
+
return now >= running.from + running.effect.duration + EFFECT_FADE_MS;
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
function effectOpacity(running, now) {
|
|
456
|
+
if (now < running.from) return 0;
|
|
457
|
+
const inFade = Math.min(1, (now - running.from) / 150);
|
|
458
|
+
if (running.until !== void 0) {
|
|
459
|
+
const out = Math.max(0, 1 - (now - running.until) / EFFECT_FADE_MS);
|
|
460
|
+
return inFade * out;
|
|
461
|
+
}
|
|
462
|
+
if (running.effect.duration !== void 0) {
|
|
463
|
+
const remaining = running.from + running.effect.duration - now;
|
|
464
|
+
if (remaining < 0) {
|
|
465
|
+
const out = Math.max(0, 1 + remaining / EFFECT_FADE_MS);
|
|
466
|
+
return inFade * out;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return inFade;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/scene/behavior.ts
|
|
473
|
+
function evaluateWriggle(t, freq, amount, octaves = 3, seed = 42) {
|
|
474
|
+
return fractalWiggle(freq, amount, octaves, 0.5, t, seed, 0);
|
|
475
|
+
}
|
|
476
|
+
function evaluateBehavior(behavior, elapsedSec, durationSec, baseValue = 0) {
|
|
477
|
+
const t = Math.max(0, elapsedSec);
|
|
478
|
+
switch (behavior.type) {
|
|
479
|
+
case "oscillate": {
|
|
480
|
+
const freq = behavior.frequency;
|
|
481
|
+
const phase = behavior.phase ?? 0;
|
|
482
|
+
const angle = 2 * Math.PI * freq * t + phase;
|
|
483
|
+
let waveVal = 0;
|
|
484
|
+
switch (behavior.wave) {
|
|
485
|
+
case "square":
|
|
486
|
+
waveVal = Math.sin(angle) >= 0 ? 1 : -1;
|
|
487
|
+
break;
|
|
488
|
+
case "triangle":
|
|
489
|
+
waveVal = 2 / Math.PI * Math.asin(Math.sin(angle));
|
|
490
|
+
break;
|
|
491
|
+
case "sawtooth": {
|
|
492
|
+
const norm = angle / (2 * Math.PI) % 1;
|
|
493
|
+
waveVal = 2 * (norm < 0 ? norm + 1 : norm) - 1;
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case "sine":
|
|
497
|
+
default:
|
|
498
|
+
waveVal = Math.sin(angle);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
const offset = waveVal * behavior.amplitude;
|
|
502
|
+
return behavior.center !== void 0 ? behavior.center + offset : baseValue + offset;
|
|
503
|
+
}
|
|
504
|
+
case "ramp": {
|
|
505
|
+
const from = behavior.from ?? baseValue;
|
|
506
|
+
const to = behavior.to;
|
|
507
|
+
const p = durationSec > 0 ? Math.min(1, Math.max(0, t / durationSec)) : 1;
|
|
508
|
+
let eased = p;
|
|
509
|
+
switch (behavior.ease) {
|
|
510
|
+
case "easeIn":
|
|
511
|
+
eased = p * p;
|
|
512
|
+
break;
|
|
513
|
+
case "easeOut":
|
|
514
|
+
eased = p * (2 - p);
|
|
515
|
+
break;
|
|
516
|
+
case "easeInOut":
|
|
517
|
+
eased = p < 0.5 ? 2 * p * p : -1 + (4 - 2 * p) * p;
|
|
518
|
+
break;
|
|
519
|
+
case "linear":
|
|
520
|
+
default:
|
|
521
|
+
eased = p;
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
return from + (to - from) * eased;
|
|
525
|
+
}
|
|
526
|
+
case "rate": {
|
|
527
|
+
return baseValue + behavior.rate * t;
|
|
528
|
+
}
|
|
529
|
+
case "wriggle": {
|
|
530
|
+
const wander = evaluateWriggle(
|
|
531
|
+
t,
|
|
532
|
+
behavior.frequency,
|
|
533
|
+
behavior.amount,
|
|
534
|
+
behavior.octaves ?? 3,
|
|
535
|
+
behavior.seed ?? 42
|
|
536
|
+
);
|
|
537
|
+
return baseValue + wander;
|
|
538
|
+
}
|
|
539
|
+
case "overshoot": {
|
|
540
|
+
const from = behavior.startValue ?? baseValue;
|
|
541
|
+
const target = behavior.targetValue;
|
|
542
|
+
const cycles = behavior.cycles ?? 3;
|
|
543
|
+
const damping = behavior.damping ?? 4;
|
|
544
|
+
if (durationSec <= 0) return target;
|
|
545
|
+
const p = Math.min(1, t / durationSec);
|
|
546
|
+
if (p >= 1) return target;
|
|
547
|
+
const envelope = Math.exp(-damping * p);
|
|
548
|
+
const oscillation = Math.cos(cycles * Math.PI * p);
|
|
549
|
+
const progress = 1 - envelope * oscillation;
|
|
550
|
+
return from + (target - from) * progress;
|
|
551
|
+
}
|
|
552
|
+
case "throw": {
|
|
553
|
+
const v0 = behavior.initialVelocity;
|
|
554
|
+
const k = Math.max(1e-3, behavior.friction ?? 2);
|
|
555
|
+
const displacement = v0 / k * (1 - Math.exp(-k * t));
|
|
556
|
+
return baseValue + displacement;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/scene/report.ts
|
|
562
|
+
function reviewScene(scene, logs, options = {}) {
|
|
563
|
+
const warnings = [];
|
|
564
|
+
const notes = [];
|
|
565
|
+
const minTextPx = options.minTextPx ?? 18;
|
|
566
|
+
const totalMs = logs.length > 0 ? logs[logs.length - 1].end : 0;
|
|
567
|
+
const totalSec = (totalMs / 1e3).toFixed(1);
|
|
568
|
+
if (scene.target) {
|
|
569
|
+
const [minS, maxS] = scene.target;
|
|
570
|
+
const durS = totalMs / 1e3;
|
|
571
|
+
if (durS < minS) {
|
|
572
|
+
warnings.push(`Scene is ${totalSec}s, shorter than target minimum (${minS}s).`);
|
|
573
|
+
} else if (durS > maxS) {
|
|
574
|
+
warnings.push(`Scene is ${totalSec}s, longer than target maximum (${maxS}s).`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (const log of logs) {
|
|
578
|
+
const shotPrefix = `Shot ${log.index}:`;
|
|
579
|
+
if (log.failed.length > 0) {
|
|
580
|
+
warnings.push(`${shotPrefix} ${log.failed.length} command(s) failed: ${log.failed.join(", ")}`);
|
|
581
|
+
}
|
|
582
|
+
for (const f of log.framing) {
|
|
583
|
+
warnings.push(`${shotPrefix} element "${f.id}" is ${f.how} of the camera frame.`);
|
|
584
|
+
}
|
|
585
|
+
for (const [a, b] of log.overlaps) {
|
|
586
|
+
notes.push(`${shotPrefix} elements "${a}" and "${b}" overlap significantly.`);
|
|
587
|
+
}
|
|
588
|
+
if (log.smallTexts) {
|
|
589
|
+
for (const t of log.smallTexts) {
|
|
590
|
+
if (t.px < minTextPx) {
|
|
591
|
+
warnings.push(`${shotPrefix} text "${t.text}" is only ${t.px}px on screen (less than ${minTextPx}px minimum).`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
for (const id of log.underTitle) {
|
|
596
|
+
warnings.push(`${shotPrefix} element "${id}" is partially covered by the title bar.`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const lines = [
|
|
600
|
+
`# Scene Preview Review: ${scene.title || scene.name}`,
|
|
601
|
+
`**Total Duration:** ${totalSec}s (${logs.length} shots)`,
|
|
602
|
+
""
|
|
603
|
+
];
|
|
604
|
+
if (warnings.length > 0) {
|
|
605
|
+
lines.push("### \u26A0\uFE0F Warnings to Address:");
|
|
606
|
+
for (const w of warnings) lines.push(`- ${w}`);
|
|
607
|
+
lines.push("");
|
|
608
|
+
} else {
|
|
609
|
+
lines.push("\u2705 No critical layout or framing warnings.");
|
|
610
|
+
lines.push("");
|
|
611
|
+
}
|
|
612
|
+
if (notes.length > 0) {
|
|
613
|
+
lines.push("### \u2139\uFE0F Layout Notes:");
|
|
614
|
+
for (const n of notes) lines.push(`- ${n}`);
|
|
615
|
+
lines.push("");
|
|
616
|
+
}
|
|
617
|
+
lines.push("### Shot Breakdown");
|
|
618
|
+
lines.push("| Shot | Start | End | Duration | Title / Caption |");
|
|
619
|
+
lines.push("|:---:|:---:|:---:|:---:|:---|");
|
|
620
|
+
for (const log of logs) {
|
|
621
|
+
const dur = ((log.end - log.start) / 1e3).toFixed(1);
|
|
622
|
+
const text = log.title || log.caption || "\u2014";
|
|
623
|
+
lines.push(`| ${log.index} | ${(log.start / 1e3).toFixed(1)}s | ${(log.end / 1e3).toFixed(1)}s | ${dur}s | ${text.slice(0, 40)} |`);
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
totalMs,
|
|
627
|
+
warnings,
|
|
628
|
+
notes,
|
|
629
|
+
markdown: lines.join("\n")
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/scene/player.ts
|
|
634
|
+
var MemorySceneBoard = class {
|
|
635
|
+
view;
|
|
636
|
+
elMap = /* @__PURE__ */ new Map();
|
|
637
|
+
currentTitle = "";
|
|
638
|
+
currentCaption = "";
|
|
639
|
+
runningEffects = [];
|
|
640
|
+
constructor(stage = DEFAULT_STAGE) {
|
|
641
|
+
this.view = viewAt(stage.width / 2, stage.height / 2, 1, stage);
|
|
642
|
+
}
|
|
643
|
+
getView() {
|
|
644
|
+
return this.view;
|
|
645
|
+
}
|
|
646
|
+
setView(view) {
|
|
647
|
+
this.view = view;
|
|
648
|
+
}
|
|
649
|
+
elements() {
|
|
650
|
+
return Array.from(this.elMap.values());
|
|
651
|
+
}
|
|
652
|
+
runCommand(cmd) {
|
|
653
|
+
if (isElementCommand(cmd)) {
|
|
654
|
+
this.elMap.set(cmd.element.id, { ...cmd.element });
|
|
655
|
+
} else if (isUpdateCommand(cmd)) {
|
|
656
|
+
const existing = this.elMap.get(cmd.id);
|
|
657
|
+
if (existing) {
|
|
658
|
+
this.elMap.set(cmd.id, { ...existing, ...cmd.props });
|
|
659
|
+
}
|
|
660
|
+
} else if (isDeleteCommand(cmd)) {
|
|
661
|
+
this.elMap.delete(cmd.id);
|
|
662
|
+
} else if (cmd.type === "clear") {
|
|
663
|
+
this.elMap.clear();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
setTitle(text) {
|
|
667
|
+
this.currentTitle = text;
|
|
668
|
+
}
|
|
669
|
+
setCaption(text) {
|
|
670
|
+
this.currentCaption = text;
|
|
671
|
+
}
|
|
672
|
+
setEffects(effects) {
|
|
673
|
+
this.runningEffects = effects;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
function shotView(shot, elements, stage, current) {
|
|
677
|
+
const camera = shot.camera;
|
|
678
|
+
if (!camera) return void 0;
|
|
679
|
+
let view = { zoom: current.zoom, scrollX: current.scrollX, scrollY: current.scrollY };
|
|
680
|
+
let framed;
|
|
681
|
+
if ("frame" in camera) {
|
|
682
|
+
const frameIds = new Set(camera.frame);
|
|
683
|
+
const targetElements = elements.filter((el) => frameIds.has(el.id));
|
|
684
|
+
if (targetElements.length === 0) {
|
|
685
|
+
for (const cmd of shot.run ?? []) {
|
|
686
|
+
if (isElementCommand(cmd) && frameIds.has(cmd.element.id)) targetElements.push(cmd.element);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
framed = calculateFramingRect(targetElements, stage, {
|
|
690
|
+
padding: camera.padding,
|
|
691
|
+
minWidth: camera.minWidth,
|
|
692
|
+
minHeight: camera.minHeight
|
|
693
|
+
});
|
|
694
|
+
} else if ("width" in camera) {
|
|
695
|
+
framed = camera;
|
|
696
|
+
}
|
|
697
|
+
if (framed) view = frame(framed, stage);
|
|
698
|
+
const angle = cameraAngle({
|
|
699
|
+
tilt: camera.tilt ?? current.tilt,
|
|
700
|
+
turn: camera.turn ?? current.turn,
|
|
701
|
+
roll: camera.roll ?? current.roll,
|
|
702
|
+
perspective: camera.perspective ?? current.perspective
|
|
703
|
+
});
|
|
704
|
+
const angled = { ...view, ...angle };
|
|
705
|
+
return framed && isAngled(angled) ? fitAngled(angled, framed, stage) : angled;
|
|
706
|
+
}
|
|
707
|
+
function fitAngled(view, box, stage) {
|
|
708
|
+
const [cx, cy] = centreOf(view, stage);
|
|
709
|
+
const halfW = stage.width / 2;
|
|
710
|
+
const halfH = boardHeight(stage) / 2;
|
|
711
|
+
let fitted = view;
|
|
712
|
+
for (let i = 0; i < 4; i++) {
|
|
713
|
+
let spread = 0;
|
|
714
|
+
for (const corner of [[box.x, box.y], [box.x + box.width, box.y], [box.x, box.y + box.height], [box.x + box.width, box.y + box.height]]) {
|
|
715
|
+
const p = projectPoint(fitted, stage, corner);
|
|
716
|
+
spread = p ? Math.max(spread, Math.abs(p[0] - halfW) / halfW, Math.abs(p[1] - halfH) / halfH) : Infinity;
|
|
717
|
+
if (!p) break;
|
|
718
|
+
}
|
|
719
|
+
if (spread <= 1.001) break;
|
|
720
|
+
const zoom = fitted.zoom / (Number.isFinite(spread) ? spread : 2);
|
|
721
|
+
fitted = { ...fitted, ...viewAt(cx, cy, zoom, stage) };
|
|
722
|
+
}
|
|
723
|
+
return fitted;
|
|
724
|
+
}
|
|
725
|
+
async function playScene(scene, board, clock, options = {}) {
|
|
726
|
+
const stage = {
|
|
727
|
+
width: scene.stage?.width ?? DEFAULT_STAGE.width,
|
|
728
|
+
height: scene.stage?.height ?? DEFAULT_STAGE.height,
|
|
729
|
+
caption: scene.stage?.caption ?? DEFAULT_STAGE.caption
|
|
730
|
+
};
|
|
731
|
+
const shots = [];
|
|
732
|
+
const begin = clock.now();
|
|
733
|
+
let runningEffects = [];
|
|
734
|
+
const endsAt = /* @__PURE__ */ new Map();
|
|
735
|
+
const updateEffects = (next) => {
|
|
736
|
+
runningEffects = next.filter((r) => !effectGone(r, clock.now()));
|
|
737
|
+
board.setEffects?.(runningEffects);
|
|
738
|
+
};
|
|
739
|
+
for (const [idx, shot] of scene.shots.entries()) {
|
|
740
|
+
const shotStart = clock.now() - begin;
|
|
741
|
+
const failedCmds = [];
|
|
742
|
+
const stopIds = new Set(shot.stopEffects ?? []);
|
|
743
|
+
if (stopIds.size > 0 || runningEffects.some((r) => endsAt.get(r) === idx)) {
|
|
744
|
+
const now = clock.now();
|
|
745
|
+
updateEffects(
|
|
746
|
+
runningEffects.map(
|
|
747
|
+
(r) => r.until === void 0 && (stopIds.has(r.effect.id) || endsAt.get(r) === idx) ? { ...r, until: now } : r
|
|
748
|
+
)
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
const targetView = shotView(shot, board.elements(), stage, board.getView());
|
|
752
|
+
if (targetView) {
|
|
753
|
+
if (idx === 0) {
|
|
754
|
+
board.setView(targetView);
|
|
755
|
+
} else {
|
|
756
|
+
const fromView = board.getView();
|
|
757
|
+
const strength = shot.camera?.motionBlur ?? 0;
|
|
758
|
+
const steps = 15;
|
|
759
|
+
const stepMs = CAMERA_MS / steps;
|
|
760
|
+
let last = fromView;
|
|
761
|
+
for (let s = 1; s <= steps; s++) {
|
|
762
|
+
const t = easeInOutCubic(s / steps);
|
|
763
|
+
const view = between(fromView, targetView, t, stage);
|
|
764
|
+
board.setView(s < steps ? { ...view, ...moveBlur(last, view, stage, strength) } : view);
|
|
765
|
+
last = view;
|
|
766
|
+
await clock.sleep(stepMs);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (shot.title !== void 0) {
|
|
771
|
+
board.setTitle?.(shot.title, 1);
|
|
772
|
+
}
|
|
773
|
+
if (shot.caption !== void 0) {
|
|
774
|
+
board.setCaption?.(shot.caption);
|
|
775
|
+
}
|
|
776
|
+
const startEffects = (shot.effects ?? []).filter((e) => e.at === "start");
|
|
777
|
+
if (startEffects.length > 0) {
|
|
778
|
+
const now = clock.now();
|
|
779
|
+
const added = startEffects.map((effect) => ({ effect, from: now }));
|
|
780
|
+
for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);
|
|
781
|
+
updateEffects([...runningEffects, ...added]);
|
|
782
|
+
}
|
|
783
|
+
for (const cmd of shot.run ?? []) {
|
|
784
|
+
try {
|
|
785
|
+
await board.runCommand(cmd);
|
|
786
|
+
} catch (err) {
|
|
787
|
+
failedCmds.push(err.message || String(err));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const drawnEffects = (shot.effects ?? []).filter((e) => e.at !== "start");
|
|
791
|
+
if (drawnEffects.length > 0) {
|
|
792
|
+
const now = clock.now();
|
|
793
|
+
const added = drawnEffects.map((effect) => ({ effect, from: now }));
|
|
794
|
+
for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);
|
|
795
|
+
updateEffects([...runningEffects, ...added]);
|
|
796
|
+
}
|
|
797
|
+
const drawnTime = clock.now() - begin;
|
|
798
|
+
const check = options.check ? checkShot(shot, board.elements(), board.getView(), stage, shot.title) : { framing: [], overlaps: [], edges: [], underTitle: [] };
|
|
799
|
+
const holdMs = shot.hold ?? 800;
|
|
800
|
+
const behaviors = shot.behaviors ?? [];
|
|
801
|
+
if (behaviors.length > 0) {
|
|
802
|
+
const initialProps = /* @__PURE__ */ new Map();
|
|
803
|
+
for (const b of behaviors) {
|
|
804
|
+
const key = `${b.target}:${b.property}`;
|
|
805
|
+
const el = board.elements().find((e) => e.id === b.target);
|
|
806
|
+
if (el && typeof el[b.property] === "number") {
|
|
807
|
+
initialProps.set(key, el[b.property]);
|
|
808
|
+
} else {
|
|
809
|
+
initialProps.set(key, 0);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const stepMs = 50;
|
|
813
|
+
const steps = Math.max(1, Math.round(holdMs / stepMs));
|
|
814
|
+
const actualStepMs = holdMs / steps;
|
|
815
|
+
const durationSec = holdMs / 1e3;
|
|
816
|
+
for (let s = 1; s <= steps; s++) {
|
|
817
|
+
await clock.sleep(actualStepMs);
|
|
818
|
+
const elapsedSec = s * actualStepMs / 1e3;
|
|
819
|
+
for (const b of behaviors) {
|
|
820
|
+
const baseVal = initialProps.get(`${b.target}:${b.property}`) ?? 0;
|
|
821
|
+
const nextVal = evaluateBehavior(b, elapsedSec, durationSec, baseVal);
|
|
822
|
+
board.runCommand({
|
|
823
|
+
type: "update",
|
|
824
|
+
id: b.target,
|
|
825
|
+
props: { [b.property]: nextVal }
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
} else {
|
|
830
|
+
await clock.sleep(holdMs);
|
|
831
|
+
}
|
|
832
|
+
const shotEnd = clock.now() - begin;
|
|
833
|
+
const log = {
|
|
834
|
+
...check,
|
|
835
|
+
index: idx + 1,
|
|
836
|
+
start: shotStart,
|
|
837
|
+
drawn: drawnTime,
|
|
838
|
+
end: shotEnd,
|
|
839
|
+
caption: shot.caption,
|
|
840
|
+
title: shot.title,
|
|
841
|
+
failed: failedCmds,
|
|
842
|
+
effects: runningEffects.map((r) => r.effect.id)
|
|
843
|
+
};
|
|
844
|
+
shots.push(log);
|
|
845
|
+
if (options.afterShot) {
|
|
846
|
+
await options.afterShot(log);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const duration = clock.now() - begin;
|
|
850
|
+
const ok = shots.every((s) => s.failed.length === 0 && s.framing.every((f) => f.how !== "outside"));
|
|
851
|
+
return { ok, duration, shots };
|
|
852
|
+
}
|
|
853
|
+
async function simulateScene(scene, options = {}) {
|
|
854
|
+
const clock = new ManualClock();
|
|
855
|
+
const board = new MemorySceneBoard();
|
|
856
|
+
let done = false;
|
|
857
|
+
let playError = null;
|
|
858
|
+
const playPromise = playScene(scene, board, clock, { check: true }).then((res) => {
|
|
859
|
+
done = true;
|
|
860
|
+
return res;
|
|
861
|
+
}).catch((err) => {
|
|
862
|
+
done = true;
|
|
863
|
+
playError = err;
|
|
864
|
+
throw err;
|
|
865
|
+
});
|
|
866
|
+
for (let i = 0; i < 5e3 && !done; i++) {
|
|
867
|
+
await clock.advance(50);
|
|
868
|
+
}
|
|
869
|
+
if (playError) throw playError;
|
|
870
|
+
const result = await playPromise;
|
|
871
|
+
const review = reviewScene(scene, result.shots, {
|
|
872
|
+
target: scene.target,
|
|
873
|
+
minTextPx: options.minTextPx
|
|
874
|
+
});
|
|
875
|
+
return { result, review };
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/scene/cameratrack.ts
|
|
879
|
+
function sceneStage(scene) {
|
|
880
|
+
return {
|
|
881
|
+
width: scene.stage?.width ?? DEFAULT_STAGE.width,
|
|
882
|
+
height: scene.stage?.height ?? DEFAULT_STAGE.height,
|
|
883
|
+
caption: scene.stage?.caption ?? DEFAULT_STAGE.caption
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function cameraTrack(scene) {
|
|
887
|
+
const stage = sceneStage(scene);
|
|
888
|
+
const board = new MemorySceneBoard(stage);
|
|
889
|
+
let view = viewAt(stage.width / 2, (stage.height - (stage.caption ?? 0)) / 2, 1, stage);
|
|
890
|
+
let startMs = 0;
|
|
891
|
+
return scene.shots.map((shot, index) => {
|
|
892
|
+
const target = shotView(shot, board.elements(), stage, view);
|
|
893
|
+
const moveMs = index > 0 && shot.camera ? CAMERA_MS : 0;
|
|
894
|
+
const from = index === 0 && target ? target : view;
|
|
895
|
+
const to = target ?? view;
|
|
896
|
+
const holdMs = shot.hold ?? 800;
|
|
897
|
+
for (const cmd of shot.run ?? []) board.runCommand(cmd);
|
|
898
|
+
const entry = {
|
|
899
|
+
shot,
|
|
900
|
+
index,
|
|
901
|
+
startMs,
|
|
902
|
+
moveMs,
|
|
903
|
+
holdMs,
|
|
904
|
+
from,
|
|
905
|
+
to,
|
|
906
|
+
motionBlur: shot.camera?.motionBlur ?? 0,
|
|
907
|
+
elements: board.elements().map((e) => ({ ...e }))
|
|
908
|
+
};
|
|
909
|
+
view = to;
|
|
910
|
+
startMs += moveMs + holdMs;
|
|
911
|
+
return entry;
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
var CAMERA_STEP_MS = CAMERA_MS / 15;
|
|
915
|
+
function cameraAt(entry, ms, stage) {
|
|
916
|
+
if (!(entry.moveMs > 0) || ms >= entry.moveMs) return entry.to;
|
|
917
|
+
const u = Math.max(0, ms) / entry.moveMs;
|
|
918
|
+
const view = between(entry.from, entry.to, easeInOutCubic(u), stage);
|
|
919
|
+
if (!(entry.motionBlur > 0)) return view;
|
|
920
|
+
const before = between(entry.from, entry.to, easeInOutCubic(Math.max(0, u - CAMERA_STEP_MS / entry.moveMs)), stage);
|
|
921
|
+
return { ...view, ...moveBlur(before, view, stage, entry.motionBlur) };
|
|
922
|
+
}
|
|
923
|
+
function cameraSamples(scene, frameRate, every = 2) {
|
|
924
|
+
const stage = sceneStage(scene);
|
|
925
|
+
const samples = [];
|
|
926
|
+
const add = (sample) => {
|
|
927
|
+
if (samples.at(-1)?.frame === sample.frame) samples.pop();
|
|
928
|
+
samples.push(sample);
|
|
929
|
+
};
|
|
930
|
+
for (const entry of cameraTrack(scene)) {
|
|
931
|
+
const start = Math.round(entry.startMs / 1e3 * frameRate);
|
|
932
|
+
const moveFrames = Math.round(entry.moveMs / 1e3 * frameRate);
|
|
933
|
+
for (let f = 0; f < moveFrames; f += every) {
|
|
934
|
+
add({ frame: start + f, view: cameraAt(entry, f / frameRate * 1e3, stage), shot: entry.index });
|
|
935
|
+
}
|
|
936
|
+
add({ frame: start + moveFrames, view: entry.to, shot: entry.index });
|
|
937
|
+
}
|
|
938
|
+
return samples;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export {
|
|
942
|
+
hash64,
|
|
943
|
+
mixSeeds,
|
|
944
|
+
unitFloat,
|
|
945
|
+
quinticFade,
|
|
946
|
+
noise1d,
|
|
947
|
+
noise3d,
|
|
948
|
+
fractalWiggle,
|
|
949
|
+
isElementCommand,
|
|
950
|
+
isUpdateCommand,
|
|
951
|
+
isDeleteCommand,
|
|
952
|
+
boundsOf,
|
|
953
|
+
overlapArea,
|
|
954
|
+
areaOf,
|
|
955
|
+
DEFAULT_STAGE,
|
|
956
|
+
CAMERA_MS,
|
|
957
|
+
DEFAULT_PERSPECTIVE,
|
|
958
|
+
MAX_TILT,
|
|
959
|
+
boardHeight,
|
|
960
|
+
viewAt,
|
|
961
|
+
centreOf,
|
|
962
|
+
frame,
|
|
963
|
+
calculateFramingRect,
|
|
964
|
+
isAngled,
|
|
965
|
+
cameraAngle,
|
|
966
|
+
between,
|
|
967
|
+
cameraTransform,
|
|
968
|
+
cameraBlur,
|
|
969
|
+
cameraFilter,
|
|
970
|
+
projectPoint,
|
|
971
|
+
motionBlur,
|
|
972
|
+
motionBlurAngle,
|
|
973
|
+
moveBlur,
|
|
974
|
+
visibleRect,
|
|
975
|
+
easeInOutCubic,
|
|
976
|
+
NO_CHECK,
|
|
977
|
+
SMALL_TEXT_PX,
|
|
978
|
+
titleBox,
|
|
979
|
+
touchedIds,
|
|
980
|
+
screenBox,
|
|
981
|
+
checkShot,
|
|
982
|
+
EFFECT_TYPES,
|
|
983
|
+
EFFECT_FADE_MS,
|
|
984
|
+
effectGone,
|
|
985
|
+
effectOpacity,
|
|
986
|
+
evaluateBehavior,
|
|
987
|
+
reviewScene,
|
|
988
|
+
MemorySceneBoard,
|
|
989
|
+
shotView,
|
|
990
|
+
playScene,
|
|
991
|
+
simulateScene,
|
|
992
|
+
sceneStage,
|
|
993
|
+
cameraTrack,
|
|
994
|
+
CAMERA_STEP_MS,
|
|
995
|
+
cameraAt,
|
|
996
|
+
cameraSamples
|
|
997
|
+
};
|
|
998
|
+
//# sourceMappingURL=chunk-WAMA45G3.js.map
|