@vosjs/studio-core 0.7.0 → 0.8.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/README.md +1 -1
- package/dist/index.js +104 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The shared layers lower as one engine `stack` entry, `STUDIO_ENTRY_ID` (`'vosso.
|
|
|
36
36
|
- **Seconds everywhere.** Zoom, speed, tilt and cam-motion spans and the cursor samples are anchored in source time, so they follow footage through trims. Overlays, audio clips and click effects are anchored in output time, so a title keeps its perceived length through a speed change. `ratedSegments`, `effectiveSegments`, `docOutputDuration` and `outputRangeToSource` are the remaps.
|
|
37
37
|
- **Normalized coordinates.** A zoom's `cx`/`cy` and an overlay's `transform.x`/`y` are fractions of the frame in `[0, 1]`, never pixels, so a document survives an aspect-ratio switch. `focusBounds` and `clampFocus` keep a focus inside the card at a given `level`.
|
|
38
38
|
- **Camera styles.** `ZOOM_STYLES` names eight camera personalities (`glide`, the default, then `focus`, `cinema`, `snappy`, `cut`, `keynote`, `drift`, `none`); a style parametrizes both the planner and the camera motion, and `doc.zoomParams` layers per-document overrides.
|
|
39
|
-
- **The stage.** `CARD_FOV`, `CARD_Z`, `OVERLAY_Z`, `BACKGROUND_Z` and `planeSizeAtDepth` define the frustum-filling planes the program draws (background, the tiltable card, the overlay); `computeCardLayout` and `docCardLayout` are the host-side mirror of the program's card math, pinned to it by tests.
|
|
39
|
+
- **The stage.** `CARD_FOV`, `CARD_Z`, `OVERLAY_Z`, `BACKGROUND_Z` and `planeSizeAtDepth` define the frustum-filling planes the program draws (background, the tiltable card, the overlay); `cardVisibleExtent` and `cardOverscanFor` are how far a posed card is seen past the frame, which is what the card layer's canvas and plane grow by (so a lean over zoomed footage never shows the texture's edge); `computeCardLayout` and `docCardLayout` are the host-side mirror of the program's card math, pinned to it by tests.
|
|
40
40
|
- **Backdrops.** `withBackdrop(frame, backdrop)` and `backdropMedia` put a looping video or a still behind the card, output-anchored modulo its duration. The package carries the mechanism and `BASE_FRAME_STYLE`; which loop a host opens on is the host's choice.
|
|
41
41
|
|
|
42
42
|
## Seeing a recording: the digest
|
package/dist/index.js
CHANGED
|
@@ -1266,7 +1266,8 @@ var CARD_FOV = 30;
|
|
|
1266
1266
|
var OVERLAY_Z = -2;
|
|
1267
1267
|
var CARD_Z = -4;
|
|
1268
1268
|
var BACKGROUND_Z = -6;
|
|
1269
|
-
var
|
|
1269
|
+
var CARD_OVERSCAN_STEP = 0.05;
|
|
1270
|
+
var CARD_OVERSCAN_MAX = 4;
|
|
1270
1271
|
var CAMERA_NEAR = 0.1;
|
|
1271
1272
|
var CAMERA_FAR = 100;
|
|
1272
1273
|
function planeSizeAtDepth(distance, fovDeg, aspect) {
|
|
@@ -1813,7 +1814,11 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
1813
1814
|
if (olv && olv.length && typeof FontFace !== 'undefined') {
|
|
1814
1815
|
try {
|
|
1815
1816
|
const faces = ctx.data.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}
|
|
1817
|
+
// Mark what SETUP registers, so ON_FRAME's live path (a text overlay
|
|
1818
|
+
// arriving through SET_DATA) never adds the same face twice.
|
|
1819
|
+
const ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})
|
|
1816
1820
|
const loads = faces.map((f) => {
|
|
1821
|
+
ofSet[f.family + '|' + f.weight] = 1
|
|
1817
1822
|
const ff = new FontFace(f.family, 'url(' + f.url + ')', { weight: String(f.weight) })
|
|
1818
1823
|
document.fonts.add(ff)
|
|
1819
1824
|
return ff.load()
|
|
@@ -2273,20 +2278,37 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
2273
2278
|
continue
|
|
2274
2279
|
}
|
|
2275
2280
|
var olPx = ol.fs * olMS * s
|
|
2276
|
-
//
|
|
2277
|
-
//
|
|
2278
|
-
//
|
|
2279
|
-
|
|
2281
|
+
// Faces arriving LIVE: SET_DATA never re-runs SETUP, so the first text
|
|
2282
|
+
// overlay a session gains registers the house faces here (a title added
|
|
2283
|
+
// to a fresh document used to paint in the stack's system fallback until
|
|
2284
|
+
// the next cold load), and an override face lazy-loads the same way.
|
|
2285
|
+
// Keyed per window, so SETUP's cold-load faces and repeat frames never
|
|
2286
|
+
// re-register; fail-open, frames repaint as each face lands.
|
|
2287
|
+
if (typeof FontFace !== 'undefined') {
|
|
2280
2288
|
var ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})
|
|
2281
|
-
var
|
|
2282
|
-
|
|
2283
|
-
|
|
2289
|
+
var ofBase = d.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}
|
|
2290
|
+
for (var ofi = 0; ofi < ofBase.length; ofi++) {
|
|
2291
|
+
var ofB = ofBase[ofi]
|
|
2292
|
+
var ofBK = ofB.family + '|' + ofB.weight
|
|
2293
|
+
if (ofSet[ofBK]) continue
|
|
2294
|
+
ofSet[ofBK] = 1
|
|
2284
2295
|
try {
|
|
2285
|
-
var
|
|
2286
|
-
document.fonts.add(
|
|
2287
|
-
|
|
2296
|
+
var ofBF = new FontFace(ofB.family, 'url(' + ofB.url + ')', { weight: String(ofB.weight) })
|
|
2297
|
+
document.fonts.add(ofBF)
|
|
2298
|
+
ofBF.load().catch(function () {})
|
|
2288
2299
|
} catch (e) {}
|
|
2289
2300
|
}
|
|
2301
|
+
if (ol.face) {
|
|
2302
|
+
var ofKey = ol.face.f + '|' + ol.face.w
|
|
2303
|
+
if (!ofSet[ofKey]) {
|
|
2304
|
+
ofSet[ofKey] = 1
|
|
2305
|
+
try {
|
|
2306
|
+
var ofFace = new FontFace(ol.face.f, 'url(' + ol.face.u + ')', { weight: String(ol.face.w) })
|
|
2307
|
+
document.fonts.add(ofFace)
|
|
2308
|
+
ofFace.load().catch(function () {})
|
|
2309
|
+
} catch (e) {}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2290
2312
|
}
|
|
2291
2313
|
ovC.save()
|
|
2292
2314
|
ovC.globalAlpha = olA
|
|
@@ -3561,30 +3583,81 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3561
3583
|
// the GPU texture allocated at the original dims and re-uploads the new canvas
|
|
3562
3584
|
// against stale dims \u2192 stretch/duplicate; dispose() forces a full realloc) and
|
|
3563
3585
|
// rebuilding each frustum-filling plane at the new aspect + syncing the camera.
|
|
3564
|
-
// Card OVERSCAN
|
|
3565
|
-
//
|
|
3566
|
-
//
|
|
3567
|
-
//
|
|
3568
|
-
//
|
|
3569
|
-
// card layer
|
|
3586
|
+
// Card OVERSCAN: the card's pose (the tilt track, the card-pose track)
|
|
3587
|
+
// lets the frame see MORE plane than the frame is wide on a receding
|
|
3588
|
+
// side, and the card's content (zoomed footage, a bled card, the shadow)
|
|
3589
|
+
// runs past the frame, so a plane that ends at the frame edge shows its
|
|
3590
|
+
// texture's edge, and the backdrop through it, as soon as it leans. The
|
|
3591
|
+
// card layer's canvas and plane therefore grow about the frame's centre
|
|
3592
|
+
// by the extent the two tracks can reach at the LIVE aspect: each frame
|
|
3593
|
+
// corner's viewing ray meets the posed plane and reads back in plane
|
|
3594
|
+
// units; the extremes of both tracks (largest lean per axis, smallest
|
|
3595
|
+
// scale, largest rise, every sign) bound every interpolated frame. This
|
|
3596
|
+
// is the twin of stage.ts cardVisibleExtent/cardOverscanFor (pinned by
|
|
3597
|
+
// stage.test.ts + cardTilt.test.ts; change them together). A card that
|
|
3598
|
+
// never moves, and the flat stub (no card layer), stay at 1: byte- and
|
|
3599
|
+
// pixel-identical. The factor is quantised up in steps so a scrubbed
|
|
3600
|
+
// pose grows the canvas a few times, not per tick.
|
|
3570
3601
|
var d = ctx.data || {}
|
|
3571
|
-
var
|
|
3572
|
-
|
|
3573
|
-
|
|
3602
|
+
var okx = 1, oky = 1
|
|
3603
|
+
if (r.card) {
|
|
3604
|
+
var okT = d.tiltTrack, okP = d.cardPoseTrack, okKf, oki, okv
|
|
3605
|
+
var okRx = 0, okRy = 0, okSc = 1, okDy = 0
|
|
3606
|
+
okKf = okT && okT.keyframes || []
|
|
3607
|
+
for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okRx = Math.max(okRx, Math.abs(okv[0] || 0)); okRy = Math.max(okRy, Math.abs(okv[1] || 0)) }
|
|
3608
|
+
okKf = okP && okP.keyframes || []
|
|
3609
|
+
for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okSc = Math.min(okSc, okv[0] == null ? 1 : okv[0]); okDy = Math.max(okDy, Math.abs(okv[1] || 0)) }
|
|
3610
|
+
if (okRx || okRy || okSc < 1 || okDy) {
|
|
3611
|
+
var okD = ${Math.abs(CARD_Z)}, okMax = ${CARD_OVERSCAN_MAX}, okStep = ${CARD_OVERSCAN_STEP}
|
|
3612
|
+
var okHh = okD * Math.tan(${CARD_FOV} * Math.PI / 180 / 2), okHw = okHh * W / H
|
|
3613
|
+
// [max |x|, max |y|] of the frame corners on the posed plane, in
|
|
3614
|
+
// half-plane units (R = Rx \xB7 Ry, three.js Euler XYZ with rz = 0).
|
|
3615
|
+
var okE = function (rxd, ryd, sc, dy) {
|
|
3616
|
+
var rx = rxd * Math.PI / 180, ry = ryd * Math.PI / 180
|
|
3617
|
+
var cx = Math.cos(rx), sx = Math.sin(rx), cy = Math.cos(ry), sy = Math.sin(ry)
|
|
3618
|
+
var scl = Math.max(0.05, sc), py = dy * 2 * okHh
|
|
3619
|
+
var n0 = sy, n1 = -sx * cy, n2 = cx * cy
|
|
3620
|
+
var nPos = n1 * py - n2 * okD
|
|
3621
|
+
var ex = 0, ey = 0
|
|
3622
|
+
for (var kx = -1; kx <= 1; kx += 2) for (var ky = -1; ky <= 1; ky += 2) {
|
|
3623
|
+
var d0 = kx * okHw, d1 = ky * okHh, d2 = -okD
|
|
3624
|
+
var nd = n0 * d0 + n1 * d1 + n2 * d2
|
|
3625
|
+
var lam = nd === 0 ? -1 : nPos / nd
|
|
3626
|
+
if (!(lam > 0)) { ex = okMax; ey = okMax; continue }
|
|
3627
|
+
var q0 = lam * d0, q1 = lam * d1 - py, q2 = lam * d2 + okD
|
|
3628
|
+
ex = Math.max(ex, Math.abs((cy * q0 + sx * sy * q1 - cx * sy * q2) / scl / okHw))
|
|
3629
|
+
ey = Math.max(ey, Math.abs((cx * q1 + sx * q2) / scl / okHh))
|
|
3630
|
+
}
|
|
3631
|
+
return [Math.min(okMax, ex), Math.min(okMax, ey)]
|
|
3632
|
+
}
|
|
3633
|
+
for (var okA = -1; okA <= 1; okA += 2) for (var okB = -1; okB <= 1; okB += 2) for (var okC = -1; okC <= 1; okC += 2) {
|
|
3634
|
+
var okR = okE(okA * okRx, okB * okRy, okSc, okC * okDy)
|
|
3635
|
+
okx = Math.max(okx, okR[0]); oky = Math.max(oky, okR[1])
|
|
3636
|
+
}
|
|
3637
|
+
var okQ = function (k) { if (!(k > 1)) return 1; var q = Math.ceil(Math.round((k + okStep / 10) * 1000) / (okStep * 1000)) * okStep; return Math.min(okMax, Math.round(q * 1000) / 1000) }
|
|
3638
|
+
okx = okQ(okx); oky = okQ(oky)
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
// The canvas grows by an EVEN number of pixels on each axis so the frame
|
|
3642
|
+
// region sits on whole pixels inside it (a half-pixel base translation
|
|
3643
|
+
// would resample every draw).
|
|
3644
|
+
var cW = W + 2 * Math.round(W * (okx - 1) / 2), cH = H + 2 * Math.round(H * (oky - 1) / 2)
|
|
3574
3645
|
if (cv.width !== cW || cv.height !== cH) {
|
|
3575
3646
|
var THREE = ctx.THREE
|
|
3576
3647
|
var aspect = W / H
|
|
3577
3648
|
var cm = ctx.camera
|
|
3578
3649
|
if (cm && cm.isPerspectiveCamera) { cm.aspect = aspect; cm.updateProjectionMatrix() }
|
|
3579
|
-
|
|
3650
|
+
// Each layer's canvas size and its plane's per-axis growth; the card's
|
|
3651
|
+
// plane grows by exactly its canvas's ratio, so a texel stays a pixel.
|
|
3652
|
+
var lyr = [[bg, ${BACKGROUND_Z}, W, H], [card, ${CARD_Z}, cW, cH], [ov, ${OVERLAY_Z}, W, H]]
|
|
3580
3653
|
for (var Li = 0; Li < lyr.length; Li++) {
|
|
3581
|
-
var Ly = lyr[Li][0], Lz = lyr[Li][1],
|
|
3582
|
-
if (Ly.canvas) { Ly.canvas.width =
|
|
3654
|
+
var Ly = lyr[Li][0], Lz = lyr[Li][1], Lw = lyr[Li][2], Lhp = lyr[Li][3]
|
|
3655
|
+
if (Ly.canvas) { Ly.canvas.width = Lw; Ly.canvas.height = Lhp }
|
|
3583
3656
|
if (Ly.texture && Ly.texture.dispose) Ly.texture.dispose()
|
|
3584
3657
|
if (Ly.mesh && THREE) {
|
|
3585
3658
|
if (Ly.mesh.geometry && Ly.mesh.geometry.dispose) Ly.mesh.geometry.dispose()
|
|
3586
|
-
var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
|
|
3587
|
-
Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect, Lh)
|
|
3659
|
+
var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
|
|
3660
|
+
Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect * (Lw / W), Lh * (Lhp / H))
|
|
3588
3661
|
}
|
|
3589
3662
|
}
|
|
3590
3663
|
}
|
|
@@ -3908,9 +3981,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3908
3981
|
// the contain-fit card shows the background layer through the plane's alpha.
|
|
3909
3982
|
// Under overscan the canvas is larger than the frame and every frame-space
|
|
3910
3983
|
// draw below rides a base translation to its centre.
|
|
3911
|
-
|
|
3984
|
+
var okOn = cW !== W || cH !== H
|
|
3985
|
+
if (okOn) c.setTransform(1, 0, 0, 1, 0, 0)
|
|
3912
3986
|
c.clearRect(0, 0, cv.width, cv.height)
|
|
3913
|
-
if (
|
|
3987
|
+
if (okOn) c.setTransform(1, 0, 0, 1, (cW - W) / 2, (cH - H) / 2)
|
|
3914
3988
|
// Footage is nearly always drawn SMALLER than it was recorded (a 1920
|
|
3915
3989
|
// recording in a padded 1080 card, or a 440-wide tile): the default
|
|
3916
3990
|
// bilinear tap aliases and blurs a large downscale; 'high' resamples.
|
|
@@ -4421,8 +4495,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4421
4495
|
card.mesh.rotation.y = ry
|
|
4422
4496
|
// The card's pose through an entrance or an end card: [scale, dy,
|
|
4423
4497
|
// opacity]; absent = the rest pose, exactly as before the track existed.
|
|
4498
|
+
// dy is a fraction of the FRAME plane's height (not the geometry's,
|
|
4499
|
+
// which the overscan grows).
|
|
4424
4500
|
var cpk = d.cardPoseTrack
|
|
4425
|
-
var cpH =
|
|
4501
|
+
var cpH = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
|
|
4426
4502
|
if (cpk && cpk.keyframes && cpk.keyframes.length && card.mesh.scale && card.mesh.position) {
|
|
4427
4503
|
var cpv = TL.sample(cpk, t, TL.lerpArray)
|
|
4428
4504
|
card.mesh.scale.x = cpv[0]; card.mesh.scale.y = cpv[0]
|