Fix WebGL reveal pacing on spanning pages and page-reveal-on-flip
- Reveal timing is now word-proportional per page: when a block's reveal only covers part of the block (the continuation spread is not paginated at reveal time), the page reveals only its share of the TTS, offset by the words before it. The right page no longer absorbs the whole TTS before flipping; it flips at normal pace and the continuation resumes on the next spread while TTS plays. No effect when the regions already cover the whole block (unified plan / one page). - Page flip start now shows the target spread's same-side page beneath the lifting page (revealed as it turns away) instead of a blank that pops in after the flip. Deferred (pending-reveal) sides stay blank so the masked reveal still lands via activate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -679,13 +679,24 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
const timedRegions = [];
|
const timedRegions = [];
|
||||||
const textRegions = sortedRegions.filter(region => !(region.fixedDurationMs > 0));
|
const textRegions = sortedRegions.filter(region => !(region.fixedDurationMs > 0));
|
||||||
const fixedRegions = sortedRegions.filter(region => region.fixedDurationMs > 0);
|
const fixedRegions = sortedRegions.filter(region => region.fixedDurationMs > 0);
|
||||||
let fallbackDelay = 0;
|
|
||||||
const totalArea = textRegions.reduce((sum, region) => sum + Math.max(1, region.timingArea || region.area), 0);
|
const totalArea = textRegions.reduce((sum, region) => sum + Math.max(1, region.timingArea || region.area), 0);
|
||||||
const lineHeight = Math.max(1, Number(this.metrics?.typographyLineHeightPx || 1));
|
const lineHeight = Math.max(1, Number(this.metrics?.typographyLineHeightPx || 1));
|
||||||
const estimatedTextWidth = totalArea / lineHeight;
|
const estimatedTextWidth = totalArea / lineHeight;
|
||||||
const totalDuration = requestedTotalDuration > 1
|
const baseDuration = requestedTotalDuration > 1
|
||||||
? requestedTotalDuration
|
? requestedTotalDuration
|
||||||
: Math.max(800, estimatedTextWidth * 16);
|
: Math.max(800, estimatedTextWidth * 16);
|
||||||
|
// Word-proportional scaling: these regions may cover only part of the block (the
|
||||||
|
// rest is on another spread this reveal does not include). Reveal only this portion's
|
||||||
|
// share of the block TTS, offset by the words before it, so the page reveals at
|
||||||
|
// normal pace and flips when its words are spoken — the continuation then resumes on
|
||||||
|
// the next spread instead of the page absorbing the whole TTS. When the regions cover
|
||||||
|
// the whole block (unified plan or single-page block) this is a no-op.
|
||||||
|
const totalBlockWords = Array.isArray(animation.wordTimings) ? animation.wordTimings.length : 0;
|
||||||
|
const collectedWords = textRegions.reduce((sum, region) => sum + Math.max(0, Number(region.blockWordCount || 0)), 0);
|
||||||
|
const wordsBefore = textRegions.reduce((min, region) => Math.min(min, Math.max(0, Number(region.blockWordStart || 0))), Number.POSITIVE_INFINITY);
|
||||||
|
const useWordShare = totalBlockWords > 0 && collectedWords > 0 && collectedWords < totalBlockWords;
|
||||||
|
const totalDuration = useWordShare ? baseDuration * (collectedWords / totalBlockWords) : baseDuration;
|
||||||
|
let fallbackDelay = useWordShare && Number.isFinite(wordsBefore) ? baseDuration * (wordsBefore / totalBlockWords) : 0;
|
||||||
textRegions.forEach((region) => {
|
textRegions.forEach((region) => {
|
||||||
const duration = totalArea > 0
|
const duration = totalArea > 0
|
||||||
? Math.max(1, totalDuration * (Math.max(1, region.timingArea || region.area) / totalArea))
|
? Math.max(1, totalDuration * (Math.max(1, region.timingArea || region.area) / totalArea))
|
||||||
|
|||||||
+15
-14
@@ -3114,20 +3114,21 @@ function prepareStaticPageForFlip(flip, prewarm = null) {
|
|||||||
hasBackTexture: Boolean(backTexture || getBlankPageTexture()),
|
hasBackTexture: Boolean(backTexture || getBlankPageTexture()),
|
||||||
sourceTextureMatchesBackTexture: sourceTexture === (backTexture || getBlankPageTexture())
|
sourceTextureMatchesBackTexture: sourceTexture === (backTexture || getBlankPageTexture())
|
||||||
};
|
};
|
||||||
if (flip.direction > 0) {
|
// The page lifts from the source side, uncovering the target spread's same-side page
|
||||||
const blankTexture = getBlankPageTexture();
|
// beneath it. Show that target page now (hidden under the lifting page at t=0, then
|
||||||
if (blankTexture && materials.rightPage.map !== blankTexture) {
|
// revealed as it turns away) instead of a blank that pops in at the end. If that side
|
||||||
clearPageReveal('right', 'page-flip-start', { preserveBaseTexture: sourceSide === 'right' });
|
// has a pending reveal (playback), keep it blank so activate lands the masked reveal.
|
||||||
materials.rightPage.map = blankTexture;
|
const revealedSide = sourceSide;
|
||||||
materials.rightPage.needsUpdate = true;
|
const revealedMaterial = revealedSide === 'left' ? materials.leftPage : materials.rightPage;
|
||||||
}
|
const revealedDeferred = Array.isArray(flip.deferRevealSides) && flip.deferRevealSides.includes(revealedSide);
|
||||||
} else if (flip.direction < 0) {
|
const revealedMeta = getPaginationPageMeta(targetPages[revealedSide]) || makeBlankPageMeta(targetPages[revealedSide]);
|
||||||
const blankTexture = getBlankPageTexture();
|
const revealedTexture = (revealedDeferred || revealedMeta.kind === 'blank')
|
||||||
if (blankTexture && materials.leftPage.map !== blankTexture) {
|
? getBlankPageTexture()
|
||||||
clearPageReveal('left', 'page-flip-start', { preserveBaseTexture: sourceSide === 'left' });
|
: (pageTextureStore?.getResidentTextureForMeta?.(revealedMeta) || getBlankPageTexture());
|
||||||
materials.leftPage.map = blankTexture;
|
clearPageReveal(revealedSide, 'page-flip-start', { preserveBaseTexture: true });
|
||||||
materials.leftPage.needsUpdate = true;
|
if (revealedTexture && revealedMaterial.map !== revealedTexture) {
|
||||||
}
|
revealedMaterial.map = revealedTexture;
|
||||||
|
revealedMaterial.needsUpdate = true;
|
||||||
}
|
}
|
||||||
markPageTextureTiming('flipTexturePreflight:ready', {
|
markPageTextureTiming('flipTexturePreflight:ready', {
|
||||||
...lastFlipTexturePreflight,
|
...lastFlipTexturePreflight,
|
||||||
|
|||||||
Reference in New Issue
Block a user