@pixerate/flair 0.2.0 → 0.4.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 +16 -0
- package/README.md +44 -8
- package/dist/catalog/index.d.ts +5 -4
- package/dist/catalog/index.js +12 -4
- package/dist/{chunk-3FAUZOGL.js → chunk-FIJOMGPG.js} +2 -2
- package/dist/{chunk-JWZCPMFG.js → chunk-GL37YCCM.js} +134 -680
- package/dist/chunk-GL37YCCM.js.map +1 -0
- 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 +12 -8
- package/dist/editor/index.js +26 -16
- 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-DtnFkpkf.d.ts} +7 -3
- package/dist/types-CsFUZZIv.d.ts +102 -0
- package/package.json +1 -1
- 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
|
@@ -8,31 +8,21 @@ import {
|
|
|
8
8
|
drawFrame,
|
|
9
9
|
resetGlCaches,
|
|
10
10
|
wrapSource
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
ManualClock
|
|
14
|
-
} from "./chunk-WMZQCUFJ.js";
|
|
11
|
+
} from "./chunk-VA74HBPK.js";
|
|
15
12
|
import {
|
|
16
13
|
CAMERA_MS,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
isDeleteCommand,
|
|
25
|
-
isElementCommand,
|
|
26
|
-
isUpdateCommand,
|
|
27
|
-
overlapArea,
|
|
28
|
-
viewAt,
|
|
29
|
-
visibleRect
|
|
30
|
-
} from "./chunk-36DTNSHU.js";
|
|
14
|
+
CAMERA_STEP_MS,
|
|
15
|
+
EFFECT_TYPES,
|
|
16
|
+
MAX_TILT,
|
|
17
|
+
cameraAt,
|
|
18
|
+
cameraTrack,
|
|
19
|
+
sceneStage
|
|
20
|
+
} from "./chunk-WAMA45G3.js";
|
|
31
21
|
import {
|
|
32
22
|
controlValue,
|
|
33
23
|
imageUrls,
|
|
34
24
|
parseControls
|
|
35
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-WC3VADZC.js";
|
|
36
26
|
|
|
37
27
|
// src/shader/renderer.ts
|
|
38
28
|
var ShaderRenderer = class {
|
|
@@ -48,6 +38,7 @@ var ShaderRenderer = class {
|
|
|
48
38
|
frame = 0;
|
|
49
39
|
fpsFrames = 0;
|
|
50
40
|
fpsSince = 0;
|
|
41
|
+
ownsCanvas = false;
|
|
51
42
|
resizeObserver;
|
|
52
43
|
mouse = [0, 0, 0, 0];
|
|
53
44
|
disposed = false;
|
|
@@ -66,11 +57,21 @@ var ShaderRenderer = class {
|
|
|
66
57
|
onContextLost;
|
|
67
58
|
/** Called when an image control's URL cannot be loaded; the control shows black. */
|
|
68
59
|
onImageError;
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Creates a canvas in `target` (with `className`), or attaches to an existing
|
|
62
|
+
* `HTMLCanvasElement`, and starts the animation loop. Size the canvas with CSS.
|
|
63
|
+
*/
|
|
64
|
+
constructor(target, className = "") {
|
|
65
|
+
let canvas;
|
|
66
|
+
if (target instanceof HTMLCanvasElement || "tagName" in target && target.tagName === "CANVAS") {
|
|
67
|
+
canvas = target;
|
|
68
|
+
if (className) canvas.className = className;
|
|
69
|
+
} else {
|
|
70
|
+
canvas = document.createElement("canvas");
|
|
71
|
+
if (className) canvas.className = className;
|
|
72
|
+
target.appendChild(canvas);
|
|
73
|
+
this.ownsCanvas = true;
|
|
74
|
+
}
|
|
74
75
|
this.canvas = canvas;
|
|
75
76
|
this.gl = createContext(canvas, true);
|
|
76
77
|
this.textures = this.gl && this.textureCache(this.gl);
|
|
@@ -190,7 +191,7 @@ var ShaderRenderer = class {
|
|
|
190
191
|
this.textures = null;
|
|
191
192
|
this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
192
193
|
this.gl = null;
|
|
193
|
-
this.canvas.remove();
|
|
194
|
+
if (this.ownsCanvas) this.canvas.remove();
|
|
194
195
|
}
|
|
195
196
|
resize() {
|
|
196
197
|
const w = Math.max(1, Math.round(this.canvas.clientWidth * this.pixelRatio));
|
|
@@ -1039,228 +1040,6 @@ function getGlslBlendFunction(mode) {
|
|
|
1039
1040
|
}
|
|
1040
1041
|
}
|
|
1041
1042
|
|
|
1042
|
-
// src/time/noise.ts
|
|
1043
|
-
var GOLDEN_GAMMA = 0x9e3779b97f4a7c15n;
|
|
1044
|
-
var MULT1 = 0xbf58476d1ce4e5b9n;
|
|
1045
|
-
var MULT2 = 0x94d049bb133111ebn;
|
|
1046
|
-
var MASK64 = 0xffffffffffffffffn;
|
|
1047
|
-
var BASE_MIX = 0x2545f4914f6cdd1dn;
|
|
1048
|
-
var POW2_53 = 9007199254740992;
|
|
1049
|
-
function hash64(x) {
|
|
1050
|
-
let v = x + GOLDEN_GAMMA & MASK64;
|
|
1051
|
-
v = (v ^ v >> 30n) * MULT1 & MASK64;
|
|
1052
|
-
v = (v ^ v >> 27n) * MULT2 & MASK64;
|
|
1053
|
-
return (v ^ v >> 31n) & MASK64;
|
|
1054
|
-
}
|
|
1055
|
-
function mixSeeds(parts) {
|
|
1056
|
-
let h = BASE_MIX;
|
|
1057
|
-
for (const p of parts) {
|
|
1058
|
-
const val = typeof p === "bigint" ? p : BigInt(Math.floor(p));
|
|
1059
|
-
h = hash64(h ^ hash64(val & MASK64));
|
|
1060
|
-
}
|
|
1061
|
-
return h;
|
|
1062
|
-
}
|
|
1063
|
-
function unitFloat(seed) {
|
|
1064
|
-
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
1065
|
-
const hashed = hash64(s);
|
|
1066
|
-
const shifted = Number(hashed >> 11n);
|
|
1067
|
-
return shifted / POW2_53;
|
|
1068
|
-
}
|
|
1069
|
-
function quinticFade(t) {
|
|
1070
|
-
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
1071
|
-
}
|
|
1072
|
-
function lattice(seed, i) {
|
|
1073
|
-
const mixed = seed ^ hash64(i & MASK64);
|
|
1074
|
-
return unitFloat(mixed) * 2 - 1;
|
|
1075
|
-
}
|
|
1076
|
-
function noise1d(seed, x) {
|
|
1077
|
-
if (!Number.isFinite(x)) return 0;
|
|
1078
|
-
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
1079
|
-
const floorX = Math.floor(x);
|
|
1080
|
-
const u = quinticFade(x - floorX);
|
|
1081
|
-
const i = BigInt(Math.floor(floorX));
|
|
1082
|
-
const a = lattice(s, i);
|
|
1083
|
-
const b = lattice(s, i + 1n);
|
|
1084
|
-
return a + (b - a) * u;
|
|
1085
|
-
}
|
|
1086
|
-
function noise3d(x, y, z, seed = 0) {
|
|
1087
|
-
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
1088
|
-
return 0;
|
|
1089
|
-
}
|
|
1090
|
-
const s = typeof seed === "bigint" ? seed : BigInt(Math.floor(seed));
|
|
1091
|
-
const fx = Math.floor(x);
|
|
1092
|
-
const fy = Math.floor(y);
|
|
1093
|
-
const fz = Math.floor(z);
|
|
1094
|
-
const u = quinticFade(x - fx);
|
|
1095
|
-
const v = quinticFade(y - fy);
|
|
1096
|
-
const w = quinticFade(z - fz);
|
|
1097
|
-
const ix = BigInt(fx);
|
|
1098
|
-
const iy = BigInt(fy);
|
|
1099
|
-
const iz = BigInt(fz);
|
|
1100
|
-
const g = (dx, dy, dz) => {
|
|
1101
|
-
const combined = mixSeeds([s, ix + dx, iy + dy, iz + dz]);
|
|
1102
|
-
return unitFloat(combined) * 2 - 1;
|
|
1103
|
-
};
|
|
1104
|
-
const lerp = (a, b, t) => a + (b - a) * t;
|
|
1105
|
-
const x00 = lerp(g(0n, 0n, 0n), g(1n, 0n, 0n), u);
|
|
1106
|
-
const x10 = lerp(g(0n, 1n, 0n), g(1n, 1n, 0n), u);
|
|
1107
|
-
const x01 = lerp(g(0n, 0n, 1n), g(1n, 0n, 1n), u);
|
|
1108
|
-
const x11 = lerp(g(0n, 1n, 1n), g(1n, 1n, 1n), u);
|
|
1109
|
-
const y0 = lerp(x00, x10, v);
|
|
1110
|
-
const y1 = lerp(x01, x11, v);
|
|
1111
|
-
return lerp(y0, y1, w);
|
|
1112
|
-
}
|
|
1113
|
-
function fractalWiggle(freq, amp, octaves = 3, ampMult = 0.5, t = 0, seed = 42, dimension = 0) {
|
|
1114
|
-
const oct = Math.max(1, Math.min(16, Math.floor(octaves)));
|
|
1115
|
-
let currentAmp = amp;
|
|
1116
|
-
let currentFreq = freq;
|
|
1117
|
-
let sum = 0;
|
|
1118
|
-
const baseSeed = BigInt(seed);
|
|
1119
|
-
for (let k = 0; k < oct; k++) {
|
|
1120
|
-
const s = mixSeeds([baseSeed, BigInt(dimension), BigInt(k)]);
|
|
1121
|
-
const phaseOffset = unitFloat(s) * 1e3;
|
|
1122
|
-
sum += currentAmp * noise1d(s, currentFreq * t + phaseOffset);
|
|
1123
|
-
currentAmp *= ampMult;
|
|
1124
|
-
currentFreq *= 2;
|
|
1125
|
-
}
|
|
1126
|
-
return sum;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// src/scene/effects.ts
|
|
1130
|
-
var EFFECT_TYPES = [
|
|
1131
|
-
"gradient",
|
|
1132
|
-
"flow",
|
|
1133
|
-
"glow",
|
|
1134
|
-
"spotlight",
|
|
1135
|
-
"shimmer",
|
|
1136
|
-
"ripple",
|
|
1137
|
-
"timer",
|
|
1138
|
-
"counter",
|
|
1139
|
-
"highlight",
|
|
1140
|
-
"blur",
|
|
1141
|
-
"burst",
|
|
1142
|
-
"media"
|
|
1143
|
-
];
|
|
1144
|
-
var EFFECT_FADE_MS = 300;
|
|
1145
|
-
function effectGone(running, now) {
|
|
1146
|
-
if (running.until !== void 0) {
|
|
1147
|
-
return now >= running.until + EFFECT_FADE_MS;
|
|
1148
|
-
}
|
|
1149
|
-
if (running.effect.duration !== void 0) {
|
|
1150
|
-
return now >= running.from + running.effect.duration + EFFECT_FADE_MS;
|
|
1151
|
-
}
|
|
1152
|
-
return false;
|
|
1153
|
-
}
|
|
1154
|
-
function effectOpacity(running, now) {
|
|
1155
|
-
if (now < running.from) return 0;
|
|
1156
|
-
const inFade = Math.min(1, (now - running.from) / 150);
|
|
1157
|
-
if (running.until !== void 0) {
|
|
1158
|
-
const out = Math.max(0, 1 - (now - running.until) / EFFECT_FADE_MS);
|
|
1159
|
-
return inFade * out;
|
|
1160
|
-
}
|
|
1161
|
-
if (running.effect.duration !== void 0) {
|
|
1162
|
-
const remaining = running.from + running.effect.duration - now;
|
|
1163
|
-
if (remaining < 0) {
|
|
1164
|
-
const out = Math.max(0, 1 + remaining / EFFECT_FADE_MS);
|
|
1165
|
-
return inFade * out;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
return inFade;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
// src/scene/check.ts
|
|
1172
|
-
var NO_CHECK = { framing: [], overlaps: [], edges: [], underTitle: [] };
|
|
1173
|
-
var SMALL_TEXT_PX = 24;
|
|
1174
|
-
function titleBox(title, stage) {
|
|
1175
|
-
const width = Math.min(stage.width, title.length * 20 + 64);
|
|
1176
|
-
return { x: (stage.width - width) / 2, y: 24, width, height: 60 };
|
|
1177
|
-
}
|
|
1178
|
-
function touchedIds(shot) {
|
|
1179
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1180
|
-
for (const cmd of shot.run ?? []) {
|
|
1181
|
-
if (isElementCommand(cmd)) {
|
|
1182
|
-
ids.add(cmd.element.id);
|
|
1183
|
-
} else if ("id" in cmd && typeof cmd.id === "string") {
|
|
1184
|
-
ids.add(cmd.id);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return [...ids];
|
|
1188
|
-
}
|
|
1189
|
-
var IN_FRAME = 0.95;
|
|
1190
|
-
function checkShot(shot, elements, view, stage, title) {
|
|
1191
|
-
const frameRect = visibleRect(view, stage);
|
|
1192
|
-
const touched = new Set(touchedIds(shot));
|
|
1193
|
-
const framing = [];
|
|
1194
|
-
const edges = [];
|
|
1195
|
-
const overlaps = [];
|
|
1196
|
-
const activeElements = elements.filter((e) => !e.isDeleted && (e.opacity === void 0 || e.opacity > 0));
|
|
1197
|
-
for (const el of activeElements) {
|
|
1198
|
-
const box = boundsOf(el);
|
|
1199
|
-
const overlap = overlapArea(box, frameRect);
|
|
1200
|
-
const boxArea = areaOf(box);
|
|
1201
|
-
if (touched.has(el.id)) {
|
|
1202
|
-
if (overlap === 0) {
|
|
1203
|
-
framing.push({ id: el.id, how: "outside" });
|
|
1204
|
-
} else if (boxArea > 0 && overlap / boxArea < IN_FRAME) {
|
|
1205
|
-
framing.push({ id: el.id, how: "cut" });
|
|
1206
|
-
}
|
|
1207
|
-
} else if (overlap > 0 && boxArea > 0 && overlap / boxArea < IN_FRAME) {
|
|
1208
|
-
edges.push(el.id);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
const touchedList = activeElements.filter((e) => touched.has(e.id));
|
|
1212
|
-
for (let i = 0; i < touchedList.length; i++) {
|
|
1213
|
-
for (let j = i + 1; j < touchedList.length; j++) {
|
|
1214
|
-
const a = touchedList[i];
|
|
1215
|
-
const b = touchedList[j];
|
|
1216
|
-
const aBox = boundsOf(a);
|
|
1217
|
-
const bBox = boundsOf(b);
|
|
1218
|
-
const ol = overlapArea(aBox, bBox);
|
|
1219
|
-
const minArea = Math.min(areaOf(aBox), areaOf(bBox));
|
|
1220
|
-
if (minArea > 0 && ol / minArea > 0.3) {
|
|
1221
|
-
overlaps.push([a.id, b.id]);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
const smallTexts = [];
|
|
1226
|
-
let smallestText;
|
|
1227
|
-
for (const el of activeElements) {
|
|
1228
|
-
if (el.text && el.fontSize) {
|
|
1229
|
-
const px = Math.round(el.fontSize * view.zoom);
|
|
1230
|
-
const item = { id: el.id, text: el.text, px };
|
|
1231
|
-
if (!smallestText || px < smallestText.px) {
|
|
1232
|
-
smallestText = item;
|
|
1233
|
-
}
|
|
1234
|
-
if (px < SMALL_TEXT_PX) {
|
|
1235
|
-
smallTexts.push(item);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
const underTitle = [];
|
|
1240
|
-
if (title) {
|
|
1241
|
-
const tBox = titleBox(title, stage);
|
|
1242
|
-
const sceneTBox = {
|
|
1243
|
-
x: tBox.x / view.zoom - view.scrollX,
|
|
1244
|
-
y: tBox.y / view.zoom - view.scrollY,
|
|
1245
|
-
width: tBox.width / view.zoom,
|
|
1246
|
-
height: tBox.height / view.zoom
|
|
1247
|
-
};
|
|
1248
|
-
for (const el of activeElements) {
|
|
1249
|
-
if (overlapArea(boundsOf(el), sceneTBox) > 0) {
|
|
1250
|
-
underTitle.push(el.id);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
return {
|
|
1255
|
-
framing,
|
|
1256
|
-
overlaps,
|
|
1257
|
-
smallestText,
|
|
1258
|
-
smallTexts: smallTexts.length ? smallTexts : void 0,
|
|
1259
|
-
edges,
|
|
1260
|
-
underTitle
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
1043
|
// src/scene/validate.ts
|
|
1265
1044
|
var LONG_CAPTION = 120;
|
|
1266
1045
|
var LONG_TITLE = 60;
|
|
@@ -1297,9 +1076,22 @@ function validateScene(scene) {
|
|
|
1297
1076
|
if (!Array.isArray(shot.camera.frame) || shot.camera.frame.length === 0) {
|
|
1298
1077
|
say(shotNum, "Dynamic camera framing requires a non-empty frame array of element IDs.");
|
|
1299
1078
|
}
|
|
1300
|
-
} else if (shot.camera.width <= 0 || shot.camera.height <= 0) {
|
|
1079
|
+
} else if ("width" in shot.camera && (shot.camera.width <= 0 || shot.camera.height <= 0)) {
|
|
1301
1080
|
say(shotNum, "Camera rectangle width and height must be positive.");
|
|
1302
1081
|
}
|
|
1082
|
+
const cam = shot.camera;
|
|
1083
|
+
const angle = (name, max) => {
|
|
1084
|
+
const v = cam[name];
|
|
1085
|
+
if (v !== void 0 && !(typeof v === "number" && Math.abs(v) <= max))
|
|
1086
|
+
say(shotNum, `Camera ${name} is degrees between -${max} and ${max}.`);
|
|
1087
|
+
};
|
|
1088
|
+
angle("tilt", MAX_TILT);
|
|
1089
|
+
angle("turn", MAX_TILT);
|
|
1090
|
+
angle("roll", 180);
|
|
1091
|
+
if (cam.perspective !== void 0 && !(typeof cam.perspective === "number" && cam.perspective >= 0.3))
|
|
1092
|
+
say(shotNum, "Camera perspective is the lens distance as a multiple of the stage width, 0.3 or more (1.2 by default).");
|
|
1093
|
+
if (cam.motionBlur !== void 0 && !(typeof cam.motionBlur === "number" && cam.motionBlur >= 0 && cam.motionBlur <= 3))
|
|
1094
|
+
say(shotNum, "Camera motionBlur is 0 (none) to 3 (a whip); 1 is natural.");
|
|
1303
1095
|
}
|
|
1304
1096
|
if (shot.hold !== void 0 && shot.hold < 0) {
|
|
1305
1097
|
say(shotNum, "Shot hold cannot be negative.");
|
|
@@ -1347,381 +1139,19 @@ function validateScene(scene) {
|
|
|
1347
1139
|
return issues;
|
|
1348
1140
|
}
|
|
1349
1141
|
|
|
1350
|
-
// src/scene/
|
|
1351
|
-
function
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
if (scene.target) {
|
|
1358
|
-
const [minS, maxS] = scene.target;
|
|
1359
|
-
const durS = totalMs / 1e3;
|
|
1360
|
-
if (durS < minS) {
|
|
1361
|
-
warnings.push(`Scene is ${totalSec}s, shorter than target minimum (${minS}s).`);
|
|
1362
|
-
} else if (durS > maxS) {
|
|
1363
|
-
warnings.push(`Scene is ${totalSec}s, longer than target maximum (${maxS}s).`);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
for (const log of logs) {
|
|
1367
|
-
const shotPrefix = `Shot ${log.index}:`;
|
|
1368
|
-
if (log.failed.length > 0) {
|
|
1369
|
-
warnings.push(`${shotPrefix} ${log.failed.length} command(s) failed: ${log.failed.join(", ")}`);
|
|
1370
|
-
}
|
|
1371
|
-
for (const f of log.framing) {
|
|
1372
|
-
warnings.push(`${shotPrefix} element "${f.id}" is ${f.how} of the camera frame.`);
|
|
1373
|
-
}
|
|
1374
|
-
for (const [a, b] of log.overlaps) {
|
|
1375
|
-
notes.push(`${shotPrefix} elements "${a}" and "${b}" overlap significantly.`);
|
|
1376
|
-
}
|
|
1377
|
-
if (log.smallTexts) {
|
|
1378
|
-
for (const t of log.smallTexts) {
|
|
1379
|
-
if (t.px < minTextPx) {
|
|
1380
|
-
warnings.push(`${shotPrefix} text "${t.text}" is only ${t.px}px on screen (less than ${minTextPx}px minimum).`);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
for (const id of log.underTitle) {
|
|
1385
|
-
warnings.push(`${shotPrefix} element "${id}" is partially covered by the title bar.`);
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
const lines = [
|
|
1389
|
-
`# Scene Preview Review: ${scene.title || scene.name}`,
|
|
1390
|
-
`**Total Duration:** ${totalSec}s (${logs.length} shots)`,
|
|
1391
|
-
""
|
|
1392
|
-
];
|
|
1393
|
-
if (warnings.length > 0) {
|
|
1394
|
-
lines.push("### \u26A0\uFE0F Warnings to Address:");
|
|
1395
|
-
for (const w of warnings) lines.push(`- ${w}`);
|
|
1396
|
-
lines.push("");
|
|
1397
|
-
} else {
|
|
1398
|
-
lines.push("\u2705 No critical layout or framing warnings.");
|
|
1399
|
-
lines.push("");
|
|
1400
|
-
}
|
|
1401
|
-
if (notes.length > 0) {
|
|
1402
|
-
lines.push("### \u2139\uFE0F Layout Notes:");
|
|
1403
|
-
for (const n of notes) lines.push(`- ${n}`);
|
|
1404
|
-
lines.push("");
|
|
1405
|
-
}
|
|
1406
|
-
lines.push("### Shot Breakdown");
|
|
1407
|
-
lines.push("| Shot | Start | End | Duration | Title / Caption |");
|
|
1408
|
-
lines.push("|:---:|:---:|:---:|:---:|:---|");
|
|
1409
|
-
for (const log of logs) {
|
|
1410
|
-
const dur = ((log.end - log.start) / 1e3).toFixed(1);
|
|
1411
|
-
const text = log.title || log.caption || "\u2014";
|
|
1412
|
-
lines.push(`| ${log.index} | ${(log.start / 1e3).toFixed(1)}s | ${(log.end / 1e3).toFixed(1)}s | ${dur}s | ${text.slice(0, 40)} |`);
|
|
1413
|
-
}
|
|
1414
|
-
return {
|
|
1415
|
-
totalMs,
|
|
1416
|
-
warnings,
|
|
1417
|
-
notes,
|
|
1418
|
-
markdown: lines.join("\n")
|
|
1419
|
-
};
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// src/scene/behavior.ts
|
|
1423
|
-
function evaluateWriggle(t, freq, amount, octaves = 3, seed = 42) {
|
|
1424
|
-
return fractalWiggle(freq, amount, octaves, 0.5, t, seed, 0);
|
|
1425
|
-
}
|
|
1426
|
-
function evaluateBehavior(behavior, elapsedSec, durationSec, baseValue = 0) {
|
|
1427
|
-
const t = Math.max(0, elapsedSec);
|
|
1428
|
-
switch (behavior.type) {
|
|
1429
|
-
case "oscillate": {
|
|
1430
|
-
const freq = behavior.frequency;
|
|
1431
|
-
const phase = behavior.phase ?? 0;
|
|
1432
|
-
const angle = 2 * Math.PI * freq * t + phase;
|
|
1433
|
-
let waveVal = 0;
|
|
1434
|
-
switch (behavior.wave) {
|
|
1435
|
-
case "square":
|
|
1436
|
-
waveVal = Math.sin(angle) >= 0 ? 1 : -1;
|
|
1437
|
-
break;
|
|
1438
|
-
case "triangle":
|
|
1439
|
-
waveVal = 2 / Math.PI * Math.asin(Math.sin(angle));
|
|
1440
|
-
break;
|
|
1441
|
-
case "sawtooth": {
|
|
1442
|
-
const norm = angle / (2 * Math.PI) % 1;
|
|
1443
|
-
waveVal = 2 * (norm < 0 ? norm + 1 : norm) - 1;
|
|
1444
|
-
break;
|
|
1445
|
-
}
|
|
1446
|
-
case "sine":
|
|
1447
|
-
default:
|
|
1448
|
-
waveVal = Math.sin(angle);
|
|
1449
|
-
break;
|
|
1450
|
-
}
|
|
1451
|
-
const offset = waveVal * behavior.amplitude;
|
|
1452
|
-
return behavior.center !== void 0 ? behavior.center + offset : baseValue + offset;
|
|
1453
|
-
}
|
|
1454
|
-
case "ramp": {
|
|
1455
|
-
const from = behavior.from ?? baseValue;
|
|
1456
|
-
const to = behavior.to;
|
|
1457
|
-
const p = durationSec > 0 ? Math.min(1, Math.max(0, t / durationSec)) : 1;
|
|
1458
|
-
let eased = p;
|
|
1459
|
-
switch (behavior.ease) {
|
|
1460
|
-
case "easeIn":
|
|
1461
|
-
eased = p * p;
|
|
1462
|
-
break;
|
|
1463
|
-
case "easeOut":
|
|
1464
|
-
eased = p * (2 - p);
|
|
1465
|
-
break;
|
|
1466
|
-
case "easeInOut":
|
|
1467
|
-
eased = p < 0.5 ? 2 * p * p : -1 + (4 - 2 * p) * p;
|
|
1468
|
-
break;
|
|
1469
|
-
case "linear":
|
|
1470
|
-
default:
|
|
1471
|
-
eased = p;
|
|
1472
|
-
break;
|
|
1473
|
-
}
|
|
1474
|
-
return from + (to - from) * eased;
|
|
1475
|
-
}
|
|
1476
|
-
case "rate": {
|
|
1477
|
-
return baseValue + behavior.rate * t;
|
|
1478
|
-
}
|
|
1479
|
-
case "wriggle": {
|
|
1480
|
-
const wander = evaluateWriggle(
|
|
1481
|
-
t,
|
|
1482
|
-
behavior.frequency,
|
|
1483
|
-
behavior.amount,
|
|
1484
|
-
behavior.octaves ?? 3,
|
|
1485
|
-
behavior.seed ?? 42
|
|
1486
|
-
);
|
|
1487
|
-
return baseValue + wander;
|
|
1488
|
-
}
|
|
1489
|
-
case "overshoot": {
|
|
1490
|
-
const from = behavior.startValue ?? baseValue;
|
|
1491
|
-
const target = behavior.targetValue;
|
|
1492
|
-
const cycles = behavior.cycles ?? 3;
|
|
1493
|
-
const damping = behavior.damping ?? 4;
|
|
1494
|
-
if (durationSec <= 0) return target;
|
|
1495
|
-
const p = Math.min(1, t / durationSec);
|
|
1496
|
-
if (p >= 1) return target;
|
|
1497
|
-
const envelope = Math.exp(-damping * p);
|
|
1498
|
-
const oscillation = Math.cos(cycles * Math.PI * p);
|
|
1499
|
-
const progress = 1 - envelope * oscillation;
|
|
1500
|
-
return from + (target - from) * progress;
|
|
1501
|
-
}
|
|
1502
|
-
case "throw": {
|
|
1503
|
-
const v0 = behavior.initialVelocity;
|
|
1504
|
-
const k = Math.max(1e-3, behavior.friction ?? 2);
|
|
1505
|
-
const displacement = v0 / k * (1 - Math.exp(-k * t));
|
|
1506
|
-
return baseValue + displacement;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// src/scene/player.ts
|
|
1512
|
-
var MemorySceneBoard = class {
|
|
1513
|
-
view;
|
|
1514
|
-
elMap = /* @__PURE__ */ new Map();
|
|
1515
|
-
currentTitle = "";
|
|
1516
|
-
currentCaption = "";
|
|
1517
|
-
runningEffects = [];
|
|
1518
|
-
constructor(stage = DEFAULT_STAGE) {
|
|
1519
|
-
this.view = viewAt(stage.width / 2, stage.height / 2, 1, stage);
|
|
1520
|
-
}
|
|
1521
|
-
getView() {
|
|
1522
|
-
return this.view;
|
|
1523
|
-
}
|
|
1524
|
-
setView(view) {
|
|
1525
|
-
this.view = view;
|
|
1526
|
-
}
|
|
1527
|
-
elements() {
|
|
1528
|
-
return Array.from(this.elMap.values());
|
|
1529
|
-
}
|
|
1530
|
-
runCommand(cmd) {
|
|
1531
|
-
if (isElementCommand(cmd)) {
|
|
1532
|
-
this.elMap.set(cmd.element.id, { ...cmd.element });
|
|
1533
|
-
} else if (isUpdateCommand(cmd)) {
|
|
1534
|
-
const existing = this.elMap.get(cmd.id);
|
|
1535
|
-
if (existing) {
|
|
1536
|
-
this.elMap.set(cmd.id, { ...existing, ...cmd.props });
|
|
1537
|
-
}
|
|
1538
|
-
} else if (isDeleteCommand(cmd)) {
|
|
1539
|
-
this.elMap.delete(cmd.id);
|
|
1540
|
-
} else if (cmd.type === "clear") {
|
|
1541
|
-
this.elMap.clear();
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
setTitle(text) {
|
|
1545
|
-
this.currentTitle = text;
|
|
1546
|
-
}
|
|
1547
|
-
setCaption(text) {
|
|
1548
|
-
this.currentCaption = text;
|
|
1549
|
-
}
|
|
1550
|
-
setEffects(effects) {
|
|
1551
|
-
this.runningEffects = effects;
|
|
1552
|
-
}
|
|
1553
|
-
};
|
|
1554
|
-
async function playScene(scene, board, clock, options = {}) {
|
|
1555
|
-
const stage = {
|
|
1556
|
-
width: scene.stage?.width ?? DEFAULT_STAGE.width,
|
|
1557
|
-
height: scene.stage?.height ?? DEFAULT_STAGE.height,
|
|
1558
|
-
caption: scene.stage?.caption ?? DEFAULT_STAGE.caption
|
|
1559
|
-
};
|
|
1560
|
-
const shots = [];
|
|
1561
|
-
const begin = clock.now();
|
|
1562
|
-
let runningEffects = [];
|
|
1563
|
-
const endsAt = /* @__PURE__ */ new Map();
|
|
1564
|
-
const updateEffects = (next) => {
|
|
1565
|
-
runningEffects = next.filter((r) => !effectGone(r, clock.now()));
|
|
1566
|
-
board.setEffects?.(runningEffects);
|
|
1567
|
-
};
|
|
1568
|
-
for (const [idx, shot] of scene.shots.entries()) {
|
|
1569
|
-
const shotStart = clock.now() - begin;
|
|
1570
|
-
const failedCmds = [];
|
|
1571
|
-
const stopIds = new Set(shot.stopEffects ?? []);
|
|
1572
|
-
if (stopIds.size > 0 || runningEffects.some((r) => endsAt.get(r) === idx)) {
|
|
1573
|
-
const now = clock.now();
|
|
1574
|
-
updateEffects(
|
|
1575
|
-
runningEffects.map(
|
|
1576
|
-
(r) => r.until === void 0 && (stopIds.has(r.effect.id) || endsAt.get(r) === idx) ? { ...r, until: now } : r
|
|
1577
|
-
)
|
|
1578
|
-
);
|
|
1579
|
-
}
|
|
1580
|
-
if (shot.camera) {
|
|
1581
|
-
let targetRect;
|
|
1582
|
-
if ("frame" in shot.camera) {
|
|
1583
|
-
const frameIds = new Set(shot.camera.frame);
|
|
1584
|
-
const targetElements = board.elements().filter((el) => frameIds.has(el.id));
|
|
1585
|
-
if (targetElements.length === 0 && shot.run) {
|
|
1586
|
-
for (const cmd of shot.run) {
|
|
1587
|
-
if (isElementCommand(cmd) && frameIds.has(cmd.element.id)) {
|
|
1588
|
-
targetElements.push(cmd.element);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
targetRect = calculateFramingRect(targetElements, stage, {
|
|
1593
|
-
padding: shot.camera.padding,
|
|
1594
|
-
minWidth: shot.camera.minWidth,
|
|
1595
|
-
minHeight: shot.camera.minHeight
|
|
1596
|
-
});
|
|
1597
|
-
} else {
|
|
1598
|
-
targetRect = shot.camera;
|
|
1599
|
-
}
|
|
1600
|
-
const targetView = frame(targetRect, stage);
|
|
1601
|
-
if (idx === 0) {
|
|
1602
|
-
board.setView(targetView);
|
|
1603
|
-
} else {
|
|
1604
|
-
const fromView = board.getView();
|
|
1605
|
-
const steps = 15;
|
|
1606
|
-
const stepMs = CAMERA_MS / steps;
|
|
1607
|
-
for (let s = 1; s <= steps; s++) {
|
|
1608
|
-
const t = easeInOutCubic(s / steps);
|
|
1609
|
-
board.setView(between(fromView, targetView, t, stage));
|
|
1610
|
-
await clock.sleep(stepMs);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
if (shot.title !== void 0) {
|
|
1615
|
-
board.setTitle?.(shot.title, 1);
|
|
1616
|
-
}
|
|
1617
|
-
if (shot.caption !== void 0) {
|
|
1618
|
-
board.setCaption?.(shot.caption);
|
|
1619
|
-
}
|
|
1620
|
-
const startEffects = (shot.effects ?? []).filter((e) => e.at === "start");
|
|
1621
|
-
if (startEffects.length > 0) {
|
|
1622
|
-
const now = clock.now();
|
|
1623
|
-
const added = startEffects.map((effect) => ({ effect, from: now }));
|
|
1624
|
-
for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);
|
|
1625
|
-
updateEffects([...runningEffects, ...added]);
|
|
1626
|
-
}
|
|
1627
|
-
for (const cmd of shot.run ?? []) {
|
|
1628
|
-
try {
|
|
1629
|
-
await board.runCommand(cmd);
|
|
1630
|
-
} catch (err) {
|
|
1631
|
-
failedCmds.push(err.message || String(err));
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
const drawnEffects = (shot.effects ?? []).filter((e) => e.at !== "start");
|
|
1635
|
-
if (drawnEffects.length > 0) {
|
|
1636
|
-
const now = clock.now();
|
|
1637
|
-
const added = drawnEffects.map((effect) => ({ effect, from: now }));
|
|
1638
|
-
for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);
|
|
1639
|
-
updateEffects([...runningEffects, ...added]);
|
|
1640
|
-
}
|
|
1641
|
-
const drawnTime = clock.now() - begin;
|
|
1642
|
-
const check = options.check ? checkShot(shot, board.elements(), board.getView(), stage, shot.title) : { framing: [], overlaps: [], edges: [], underTitle: [] };
|
|
1643
|
-
const holdMs = shot.hold ?? 800;
|
|
1644
|
-
const behaviors = shot.behaviors ?? [];
|
|
1645
|
-
if (behaviors.length > 0) {
|
|
1646
|
-
const initialProps = /* @__PURE__ */ new Map();
|
|
1647
|
-
for (const b of behaviors) {
|
|
1648
|
-
const key = `${b.target}:${b.property}`;
|
|
1649
|
-
const el = board.elements().find((e) => e.id === b.target);
|
|
1650
|
-
if (el && typeof el[b.property] === "number") {
|
|
1651
|
-
initialProps.set(key, el[b.property]);
|
|
1652
|
-
} else {
|
|
1653
|
-
initialProps.set(key, 0);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
const stepMs = 50;
|
|
1657
|
-
const steps = Math.max(1, Math.round(holdMs / stepMs));
|
|
1658
|
-
const actualStepMs = holdMs / steps;
|
|
1659
|
-
const durationSec = holdMs / 1e3;
|
|
1660
|
-
for (let s = 1; s <= steps; s++) {
|
|
1661
|
-
await clock.sleep(actualStepMs);
|
|
1662
|
-
const elapsedSec = s * actualStepMs / 1e3;
|
|
1663
|
-
for (const b of behaviors) {
|
|
1664
|
-
const baseVal = initialProps.get(`${b.target}:${b.property}`) ?? 0;
|
|
1665
|
-
const nextVal = evaluateBehavior(b, elapsedSec, durationSec, baseVal);
|
|
1666
|
-
board.runCommand({
|
|
1667
|
-
type: "update",
|
|
1668
|
-
id: b.target,
|
|
1669
|
-
props: { [b.property]: nextVal }
|
|
1670
|
-
});
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
} else {
|
|
1674
|
-
await clock.sleep(holdMs);
|
|
1675
|
-
}
|
|
1676
|
-
const shotEnd = clock.now() - begin;
|
|
1677
|
-
const log = {
|
|
1678
|
-
...check,
|
|
1679
|
-
index: idx + 1,
|
|
1680
|
-
start: shotStart,
|
|
1681
|
-
drawn: drawnTime,
|
|
1682
|
-
end: shotEnd,
|
|
1683
|
-
caption: shot.caption,
|
|
1684
|
-
title: shot.title,
|
|
1685
|
-
failed: failedCmds,
|
|
1686
|
-
effects: runningEffects.map((r) => r.effect.id)
|
|
1687
|
-
};
|
|
1688
|
-
shots.push(log);
|
|
1689
|
-
if (options.afterShot) {
|
|
1690
|
-
await options.afterShot(log);
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
const duration = clock.now() - begin;
|
|
1694
|
-
const ok = shots.every((s) => s.failed.length === 0 && s.framing.every((f) => f.how !== "outside"));
|
|
1695
|
-
return { ok, duration, shots };
|
|
1696
|
-
}
|
|
1697
|
-
async function simulateScene(scene, options = {}) {
|
|
1698
|
-
const clock = new ManualClock();
|
|
1699
|
-
const board = new MemorySceneBoard();
|
|
1700
|
-
let done = false;
|
|
1701
|
-
let playError = null;
|
|
1702
|
-
const playPromise = playScene(scene, board, clock, { check: true }).then((res) => {
|
|
1703
|
-
done = true;
|
|
1704
|
-
return res;
|
|
1705
|
-
}).catch((err) => {
|
|
1706
|
-
done = true;
|
|
1707
|
-
playError = err;
|
|
1708
|
-
throw err;
|
|
1709
|
-
});
|
|
1710
|
-
for (let i = 0; i < 5e3 && !done; i++) {
|
|
1711
|
-
await clock.advance(50);
|
|
1712
|
-
}
|
|
1713
|
-
if (playError) throw playError;
|
|
1714
|
-
const result = await playPromise;
|
|
1715
|
-
const review = reviewScene(scene, result.shots, {
|
|
1716
|
-
target: scene.target,
|
|
1717
|
-
minTextPx: options.minTextPx
|
|
1142
|
+
// src/scene/standalone.ts
|
|
1143
|
+
function planShots(scene, stage) {
|
|
1144
|
+
return cameraTrack(scene).map((entry) => {
|
|
1145
|
+
const steps = Math.round(entry.moveMs / CAMERA_STEP_MS);
|
|
1146
|
+
const move = Array.from({ length: Math.max(0, steps - 1) }, (_, i) => cameraAt(entry, (i + 1) * CAMERA_STEP_MS, stage));
|
|
1147
|
+
move.push(entry.to);
|
|
1148
|
+
return { move, elements: entry.elements };
|
|
1718
1149
|
});
|
|
1719
|
-
return { result, review };
|
|
1720
1150
|
}
|
|
1721
|
-
|
|
1722
|
-
// src/scene/standalone.ts
|
|
1723
1151
|
function standaloneSceneHtml(scene) {
|
|
1152
|
+
const stage = sceneStage(scene);
|
|
1724
1153
|
const serializedScene = JSON.stringify(scene).replace(/</g, "\\u003c");
|
|
1154
|
+
const serializedPlan = JSON.stringify({ stage, cameraMs: CAMERA_MS, shots: planShots(scene, stage) }).replace(/</g, "\\u003c");
|
|
1725
1155
|
const title = scene.title || scene.name;
|
|
1726
1156
|
return `<!DOCTYPE html>
|
|
1727
1157
|
<html lang="en">
|
|
@@ -1747,7 +1177,7 @@ function standaloneSceneHtml(scene) {
|
|
|
1747
1177
|
position: relative;
|
|
1748
1178
|
width: 100%;
|
|
1749
1179
|
max-width: 1200px;
|
|
1750
|
-
aspect-ratio:
|
|
1180
|
+
aspect-ratio: ${stage.width} / ${stage.height};
|
|
1751
1181
|
background: #000;
|
|
1752
1182
|
border-radius: 12px;
|
|
1753
1183
|
overflow: hidden;
|
|
@@ -1762,6 +1192,12 @@ function standaloneSceneHtml(scene) {
|
|
|
1762
1192
|
height: 100%;
|
|
1763
1193
|
overflow: hidden;
|
|
1764
1194
|
}
|
|
1195
|
+
.board {
|
|
1196
|
+
position: absolute;
|
|
1197
|
+
inset: 0;
|
|
1198
|
+
transform-origin: 50% 50%;
|
|
1199
|
+
will-change: transform, filter;
|
|
1200
|
+
}
|
|
1765
1201
|
canvas.layer {
|
|
1766
1202
|
position: absolute;
|
|
1767
1203
|
top: 0;
|
|
@@ -1858,7 +1294,8 @@ function standaloneSceneHtml(scene) {
|
|
|
1858
1294
|
<div class="player-container">
|
|
1859
1295
|
<div class="viewport" id="viewport">
|
|
1860
1296
|
<canvas id="shaderCanvas" class="layer"></canvas>
|
|
1861
|
-
<canvas id="sceneCanvas" class="layer"></canvas>
|
|
1297
|
+
<div id="board" class="board"><canvas id="sceneCanvas" class="layer"></canvas></div>
|
|
1298
|
+
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><filter id="motionBlur" x="-10%" y="-10%" width="120%" height="120%"><feGaussianBlur id="motionBlurAmount" stdDeviation="0 0"/></filter></svg>
|
|
1862
1299
|
<div id="titleEl" class="overlay-title"></div>
|
|
1863
1300
|
<div id="captionEl" class="overlay-caption"></div>
|
|
1864
1301
|
</div>
|
|
@@ -1875,6 +1312,9 @@ function standaloneSceneHtml(scene) {
|
|
|
1875
1312
|
|
|
1876
1313
|
<script>
|
|
1877
1314
|
const scene = ${serializedScene};
|
|
1315
|
+
// Each shot's camera move (zoom, scroll, 3D angle and blur at each step) and elements, worked out in advance.
|
|
1316
|
+
const plan = ${serializedPlan};
|
|
1317
|
+
const boardEl = document.getElementById('board');
|
|
1878
1318
|
const sceneCanvas = document.getElementById('sceneCanvas');
|
|
1879
1319
|
const shaderCanvas = document.getElementById('shaderCanvas');
|
|
1880
1320
|
const ctx = sceneCanvas.getContext('2d');
|
|
@@ -1888,7 +1328,8 @@ function standaloneSceneHtml(scene) {
|
|
|
1888
1328
|
|
|
1889
1329
|
let currentShotIdx = 0;
|
|
1890
1330
|
let isPlaying = false;
|
|
1891
|
-
let shotStartTime =
|
|
1331
|
+
let shotStartTime = performance.now();
|
|
1332
|
+
let moveFrom = performance.now();
|
|
1892
1333
|
let gl = null;
|
|
1893
1334
|
let shaderProgram = null;
|
|
1894
1335
|
|
|
@@ -1970,64 +1411,98 @@ function standaloneSceneHtml(scene) {
|
|
|
1970
1411
|
progressEl.style.width = pct + '%';
|
|
1971
1412
|
}
|
|
1972
1413
|
|
|
1414
|
+
// The camera at time 'now': a step of the current shot's move, or where it settled.
|
|
1415
|
+
function cameraNow(now) {
|
|
1416
|
+
const move = plan.shots[currentShotIdx].move;
|
|
1417
|
+
const u = Math.min(1, (now - moveFrom) / plan.cameraMs);
|
|
1418
|
+
return move[Math.min(move.length - 1, Math.floor(u * move.length))];
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function applyCamera(view) {
|
|
1422
|
+
const w = boardEl.clientWidth || plan.stage.width;
|
|
1423
|
+
const angled = view.tilt || view.turn || view.roll;
|
|
1424
|
+
boardEl.style.transform = angled
|
|
1425
|
+
? 'perspective(' + (view.perspective || 1.2) * w + 'px) rotateX(' + (view.tilt || 0) + 'deg) rotateY(' + (view.turn || 0) + 'deg) rotateZ(' + (view.roll || 0) + 'deg)'
|
|
1426
|
+
: 'none';
|
|
1427
|
+
// Motion blur streaks along a pan (taken back through the roll, as the board is blurred before it turns).
|
|
1428
|
+
const blur = (view.blur || 0) * w / plan.stage.width;
|
|
1429
|
+
if (blur >= 0.05) {
|
|
1430
|
+
let sx = blur, sy = blur;
|
|
1431
|
+
if (view.blurAngle !== undefined) {
|
|
1432
|
+
const a = (view.blurAngle - (view.roll || 0)) * Math.PI / 180;
|
|
1433
|
+
sx = Math.max(blur * 0.15, blur * Math.abs(Math.cos(a)));
|
|
1434
|
+
sy = Math.max(blur * 0.15, blur * Math.abs(Math.sin(a)));
|
|
1435
|
+
}
|
|
1436
|
+
document.getElementById('motionBlurAmount').setAttribute('stdDeviation', sx.toFixed(2) + ' ' + sy.toFixed(2));
|
|
1437
|
+
boardEl.style.filter = 'url(#motionBlur)';
|
|
1438
|
+
} else {
|
|
1439
|
+
boardEl.style.filter = 'none';
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1973
1443
|
function draw() {
|
|
1974
|
-
const now = performance.now()
|
|
1975
|
-
renderShader(now);
|
|
1444
|
+
const now = performance.now();
|
|
1445
|
+
renderShader(now / 1000);
|
|
1976
1446
|
|
|
1447
|
+
const view = cameraNow(now);
|
|
1448
|
+
applyCamera(view);
|
|
1449
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1977
1450
|
ctx.clearRect(0, 0, sceneCanvas.width, sceneCanvas.height);
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
// Draw shot elements
|
|
1982
|
-
if (shot && shot.run) {
|
|
1983
|
-
for (const cmd of shot.run) {
|
|
1984
|
-
if (cmd.type === 'element' && cmd.element) {
|
|
1985
|
-
const el = cmd.element;
|
|
1986
|
-
ctx.fillStyle = el.backgroundColor || 'rgba(59, 130, 246, 0.4)';
|
|
1987
|
-
ctx.strokeStyle = el.strokeColor || '#3b82f6';
|
|
1988
|
-
ctx.lineWidth = 3;
|
|
1989
|
-
ctx.strokeRect(el.x, el.y, el.width, el.height);
|
|
1990
|
-
ctx.fillRect(el.x, el.y, el.width, el.height);
|
|
1451
|
+
const k = (sceneCanvas.width / plan.stage.width) * view.zoom;
|
|
1452
|
+
ctx.setTransform(k, 0, 0, k, k * view.scrollX, k * view.scrollY);
|
|
1991
1453
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1454
|
+
for (const el of plan.shots[currentShotIdx].elements) {
|
|
1455
|
+
ctx.fillStyle = el.backgroundColor || 'rgba(59, 130, 246, 0.4)';
|
|
1456
|
+
ctx.strokeStyle = el.strokeColor || '#3b82f6';
|
|
1457
|
+
ctx.lineWidth = 3;
|
|
1458
|
+
ctx.fillRect(el.x, el.y, el.width, el.height);
|
|
1459
|
+
ctx.strokeRect(el.x, el.y, el.width, el.height);
|
|
1460
|
+
if (el.text) {
|
|
1461
|
+
ctx.fillStyle = '#fff';
|
|
1462
|
+
ctx.font = '24px sans-serif';
|
|
1463
|
+
ctx.fillText(el.text, el.x + 16, el.y + 40);
|
|
1998
1464
|
}
|
|
1999
1465
|
}
|
|
2000
|
-
ctx.restore();
|
|
2001
1466
|
|
|
2002
|
-
|
|
1467
|
+
// Playing moves on to the next shot once this one's move and hold are over.
|
|
1468
|
+
const shot = scene.shots[currentShotIdx];
|
|
1469
|
+
if (isPlaying && now - shotStartTime > plan.cameraMs + (shot.hold ?? 800) && currentShotIdx < scene.shots.length - 1) {
|
|
1470
|
+
goTo(currentShotIdx + 1, true);
|
|
1471
|
+
}
|
|
1472
|
+
if (isPlaying || now - moveFrom < plan.cameraMs) {
|
|
2003
1473
|
requestAnimationFrame(draw);
|
|
2004
1474
|
}
|
|
2005
1475
|
}
|
|
2006
1476
|
|
|
1477
|
+
// Shows a shot: forwards it plays the camera's move into it, otherwise it cuts.
|
|
1478
|
+
function goTo(idx, animate) {
|
|
1479
|
+
currentShotIdx = idx;
|
|
1480
|
+
shotStartTime = performance.now();
|
|
1481
|
+
moveFrom = animate ? shotStartTime : shotStartTime - plan.cameraMs;
|
|
1482
|
+
updateShotUI();
|
|
1483
|
+
requestAnimationFrame(draw);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
2007
1486
|
playBtn.addEventListener('click', () => {
|
|
2008
1487
|
isPlaying = !isPlaying;
|
|
2009
1488
|
playBtn.textContent = isPlaying ? 'Pause' : 'Play';
|
|
2010
|
-
|
|
1489
|
+
// The current shot counts as settled, so its hold runs from now.
|
|
1490
|
+
if (isPlaying) {
|
|
1491
|
+
shotStartTime = performance.now() - plan.cameraMs;
|
|
1492
|
+
requestAnimationFrame(draw);
|
|
1493
|
+
}
|
|
2011
1494
|
});
|
|
2012
1495
|
|
|
2013
1496
|
prevBtn.addEventListener('click', () => {
|
|
2014
|
-
if (currentShotIdx > 0)
|
|
2015
|
-
currentShotIdx--;
|
|
2016
|
-
updateShotUI();
|
|
2017
|
-
draw();
|
|
2018
|
-
}
|
|
1497
|
+
if (currentShotIdx > 0) goTo(currentShotIdx - 1, false);
|
|
2019
1498
|
});
|
|
2020
1499
|
|
|
2021
1500
|
nextBtn.addEventListener('click', () => {
|
|
2022
|
-
if (currentShotIdx < scene.shots.length - 1)
|
|
2023
|
-
currentShotIdx++;
|
|
2024
|
-
updateShotUI();
|
|
2025
|
-
draw();
|
|
2026
|
-
}
|
|
1501
|
+
if (currentShotIdx < scene.shots.length - 1) goTo(currentShotIdx + 1, true);
|
|
2027
1502
|
});
|
|
2028
1503
|
|
|
2029
|
-
|
|
2030
|
-
|
|
1504
|
+
window.addEventListener('resize', () => requestAnimationFrame(draw));
|
|
1505
|
+
goTo(0, false);
|
|
2031
1506
|
</script>
|
|
2032
1507
|
</body>
|
|
2033
1508
|
</html>`;
|
|
@@ -2047,28 +1522,7 @@ export {
|
|
|
2047
1522
|
ALL_BLEND_MODES,
|
|
2048
1523
|
blendRgba,
|
|
2049
1524
|
getGlslBlendFunction,
|
|
2050
|
-
hash64,
|
|
2051
|
-
mixSeeds,
|
|
2052
|
-
unitFloat,
|
|
2053
|
-
quinticFade,
|
|
2054
|
-
noise1d,
|
|
2055
|
-
noise3d,
|
|
2056
|
-
fractalWiggle,
|
|
2057
|
-
EFFECT_TYPES,
|
|
2058
|
-
EFFECT_FADE_MS,
|
|
2059
|
-
effectGone,
|
|
2060
|
-
effectOpacity,
|
|
2061
|
-
NO_CHECK,
|
|
2062
|
-
SMALL_TEXT_PX,
|
|
2063
|
-
titleBox,
|
|
2064
|
-
touchedIds,
|
|
2065
|
-
checkShot,
|
|
2066
1525
|
validateScene,
|
|
2067
|
-
reviewScene,
|
|
2068
|
-
evaluateBehavior,
|
|
2069
|
-
MemorySceneBoard,
|
|
2070
|
-
playScene,
|
|
2071
|
-
simulateScene,
|
|
2072
1526
|
standaloneSceneHtml
|
|
2073
1527
|
};
|
|
2074
|
-
//# sourceMappingURL=chunk-
|
|
1528
|
+
//# sourceMappingURL=chunk-GL37YCCM.js.map
|