Fix WebGL reveal timing and flip texture readiness
This commit is contained in:
+117
-17
@@ -4,7 +4,7 @@ import { RenderPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postproces
|
||||
import { SSAOPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SSAOPass.js';
|
||||
import { SMAAPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SMAAPass.js';
|
||||
import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/OutputPass.js';
|
||||
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260610-book-timeline-d';
|
||||
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260610-book-timeline-j';
|
||||
|
||||
const canvas = document.getElementById('scene');
|
||||
canvas.style.cursor = 'grab';
|
||||
@@ -278,6 +278,7 @@ const pageRevealState = {
|
||||
};
|
||||
let pageRevealFreezeAt = null;
|
||||
const pageRevealClearLog = [];
|
||||
let scheduledBookRebuildFrame = null;
|
||||
await reportLabStep(52, 'Generating leather texture set');
|
||||
const leatherTextures = createLeatherTextures();
|
||||
await reportLabStep(56, 'Generating spine cloth texture set');
|
||||
@@ -1842,12 +1843,33 @@ function getCurrentPagePosition() {
|
||||
return spreadIndexToPagePosition(bookPaginationState.spreadIndex);
|
||||
}
|
||||
|
||||
function syncReadingProgressToCurrentPage() {
|
||||
function scheduleBookRebuild(reason = 'scheduled') {
|
||||
if (scheduledBookRebuildFrame !== null) return;
|
||||
const scheduler = typeof window.requestIdleCallback === 'function'
|
||||
? (callback) => window.requestIdleCallback(callback, { timeout: 180 })
|
||||
: requestAnimationFrame;
|
||||
scheduledBookRebuildFrame = scheduler(() => {
|
||||
scheduledBookRebuildFrame = null;
|
||||
markPageTextureTiming('bookRebuild:deferred', { reason });
|
||||
buildBook();
|
||||
syncBookControls();
|
||||
});
|
||||
}
|
||||
|
||||
function syncReadingProgressToCurrentPage(options = {}) {
|
||||
const nextProgress = THREE.MathUtils.clamp(getCurrentPagePosition() / Math.max(1, bookPageCount), 0, 1);
|
||||
if (Math.abs(nextProgress - readingProgress) < 0.0001) return;
|
||||
readingProgress = nextProgress;
|
||||
const changed = Math.abs(nextProgress - readingProgress) >= 0.0001;
|
||||
if (changed) {
|
||||
readingProgress = nextProgress;
|
||||
window.WebGLBookPreferenceBridge?.updateProgress?.(readingProgress);
|
||||
}
|
||||
if (!changed && options.rebuild !== 'defer') return;
|
||||
if (options.rebuild === 'defer') {
|
||||
scheduleBookRebuild(options.reason || 'reading-progress-sync');
|
||||
return;
|
||||
}
|
||||
if (options.rebuild === false) return;
|
||||
buildBook();
|
||||
window.WebGLBookPreferenceBridge?.updateProgress?.(readingProgress);
|
||||
}
|
||||
|
||||
function growBookIfWritableLimitReached() {
|
||||
@@ -2266,15 +2288,20 @@ function getPaginationPageMeta(pageIndex) {
|
||||
const index = Math.max(0, Math.round(Number(pageIndex || 0)));
|
||||
const spreadIndex = Math.floor(index / 2);
|
||||
const side = index % 2 === 0 ? 'left' : 'right';
|
||||
const pagination = window.moduleRegistry?.getModule?.('book-pagination') || null;
|
||||
const spread = typeof pagination?.getSpread === 'function'
|
||||
? pagination.getSpread(spreadIndex)
|
||||
: Array.isArray(pagination?.spreads)
|
||||
? pagination.spreads[spreadIndex]
|
||||
: null;
|
||||
const spread = getPaginationSpread(spreadIndex);
|
||||
return spread?.pageMeta?.[side] || null;
|
||||
}
|
||||
|
||||
function getPaginationSpread(spreadIndex) {
|
||||
const index = Math.max(0, Math.round(Number(spreadIndex || 0)));
|
||||
const pagination = window.moduleRegistry?.getModule?.('book-pagination') || null;
|
||||
return typeof pagination?.getSpread === 'function'
|
||||
? pagination.getSpread(index)
|
||||
: Array.isArray(pagination?.spreads)
|
||||
? pagination.spreads[index]
|
||||
: null;
|
||||
}
|
||||
|
||||
async function prewarmSpreadTextures(spreadIndex) {
|
||||
return pageTextureStore?.prewarmSpreadTextures?.(spreadIndex, makePageMetaForCache) || {
|
||||
spreadIndex: Math.max(0, Math.round(Number(spreadIndex || 0))),
|
||||
@@ -2314,6 +2341,8 @@ async function prewarmFlipTextures(direction, targetSpread = null) {
|
||||
const nextSpread = Number.isFinite(Number(targetSpread))
|
||||
? Math.max(0, Math.round(Number(targetSpread)))
|
||||
: Math.max(0, currentSpread + Math.sign(Number(direction || 0)));
|
||||
prepareSpreadTextureRecordsForFlip(currentSpread);
|
||||
prepareSpreadTextureRecordsForFlip(nextSpread);
|
||||
const windowMap = await prewarmNavigationTextureWindow('flip-prewarm', { targetSpread: nextSpread });
|
||||
const current = windowMap?.[currentSpread] || await prewarmSpreadTextures(currentSpread);
|
||||
const next = windowMap?.[nextSpread] || await prewarmSpreadTextures(nextSpread);
|
||||
@@ -2323,6 +2352,25 @@ async function prewarmFlipTextures(direction, targetSpread = null) {
|
||||
};
|
||||
}
|
||||
|
||||
function prepareSpreadTextureRecordsForFlip(spreadIndex) {
|
||||
const spread = getPaginationSpread(spreadIndex);
|
||||
if (!spread || typeof window.BookTextureRenderer?.drawSpread !== 'function') return false;
|
||||
if (spreadTextureRecordsReady(spread)) return true;
|
||||
window.BookTextureRenderer.drawSpread(spread, ['left', 'right'], {
|
||||
phase: 'prepare'
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function spreadTextureRecordsReady(spread = null) {
|
||||
if (!spread?.pageMeta || !pageTextureStore) return false;
|
||||
return ['left', 'right'].every((side) => {
|
||||
const meta = spread.pageMeta?.[side] || null;
|
||||
if (!meta || meta.kind === 'blank') return true;
|
||||
return Boolean(pageTextureStore.getResidentTextureForMeta?.(meta));
|
||||
});
|
||||
}
|
||||
|
||||
function takePreparedPageTexture(side, revealDetail = {}) {
|
||||
const key = getRevealCacheKey(revealDetail);
|
||||
const prepared = pageTextureStore?.takePreparedPageTexture?.(side, key) || null;
|
||||
@@ -3047,6 +3095,9 @@ function updateActiveFlips(now) {
|
||||
const targetSpread = Number.isFinite(Number(flip.targetSpread))
|
||||
? Math.max(0, Math.round(Number(flip.targetSpread)))
|
||||
: null;
|
||||
if (targetSpread !== null && !hasActivePageReveal()) {
|
||||
applyResidentSpreadTextures(targetSpread, 'page-flip-near-end');
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent('webgl-book:page-flip-near-end', {
|
||||
detail: {
|
||||
direction: flip.direction,
|
||||
@@ -3060,6 +3111,48 @@ function updateActiveFlips(now) {
|
||||
completed.forEach((flip) => finishActiveFlip(flip));
|
||||
}
|
||||
|
||||
function hasActivePageReveal() {
|
||||
return ['left', 'right'].some((side) => {
|
||||
const state = pageRevealState[side];
|
||||
if (!state) return false;
|
||||
if (state.startedAt != null) return true;
|
||||
return Array.isArray(state.blockIds) && state.blockIds.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
function applyResidentSpreadTextures(spreadIndex, reason = 'resident-spread') {
|
||||
const pageIndices = spreadPageIndices(spreadIndex);
|
||||
['left', 'right'].forEach((side) => {
|
||||
const pageIndex = pageIndices[side];
|
||||
const pageMeta = getPaginationPageMeta(pageIndex) || makeBlankPageMeta(pageIndex);
|
||||
const texture = pageMeta.kind === 'blank'
|
||||
? getBlankPageTexture()
|
||||
: pageTextureStore?.getResidentTextureForMeta?.(pageMeta);
|
||||
if (!texture) {
|
||||
pageTextureStore?.recordProblem?.({
|
||||
type: 'resident-spread-texture-missing',
|
||||
reason,
|
||||
side,
|
||||
spreadIndex,
|
||||
pageIndex,
|
||||
pageKind: pageMeta.kind
|
||||
});
|
||||
return;
|
||||
}
|
||||
const material = side === 'left' ? materials.leftPage : materials.rightPage;
|
||||
clearPageReveal(side, reason);
|
||||
if (material.map !== texture) {
|
||||
material.map = texture;
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
});
|
||||
markStaticSceneBuffersDirty();
|
||||
markPageTextureTiming('residentSpreadTextures:applied', {
|
||||
reason,
|
||||
spreadIndex
|
||||
});
|
||||
}
|
||||
|
||||
function buildFlippingPageSurface(sourceLine, destinationLine, direction, t, pageOffset = 0) {
|
||||
const widthSegments = sourceLine.points.length - 1;
|
||||
const depthSegments = 18;
|
||||
@@ -3208,6 +3301,10 @@ function createFlippingPageGeometry(surface, direction = 1) {
|
||||
const depthSegments = surface[0].length - 1;
|
||||
const sourceSide = direction > 0 ? 1 : -1;
|
||||
const targetSide = -sourceSide;
|
||||
const topPageSide = direction > 0 ? targetSide : sourceSide;
|
||||
const bottomPageSide = direction > 0 ? sourceSide : targetSide;
|
||||
const topMaterialIndex = direction > 0 ? 1 : 0;
|
||||
const bottomMaterialIndex = direction > 0 ? 0 : 1;
|
||||
const push = (point, yOffset, uv) => {
|
||||
const index = positions.length / 3;
|
||||
positions.push(point.x, point.y + yOffset, point.z);
|
||||
@@ -3221,8 +3318,8 @@ function createFlippingPageGeometry(surface, direction = 1) {
|
||||
const u = widthSegments <= 0 ? 0 : widthIndex / widthSegments;
|
||||
rowPoints.forEach((point, depthIndex) => {
|
||||
const v = depthSegments <= 0 ? 0 : depthIndex / depthSegments;
|
||||
topRow.push(push(point, pageThickness, pageUvForSide(sourceSide, u, v)));
|
||||
bottomRow.push(push(point, 0, pageUvForSide(targetSide, u, v)));
|
||||
topRow.push(push(point, pageThickness, pageUvForSide(topPageSide, u, v)));
|
||||
bottomRow.push(push(point, 0, pageUvForSide(bottomPageSide, u, v)));
|
||||
});
|
||||
topGrid.push(topRow);
|
||||
bottomGrid.push(bottomRow);
|
||||
@@ -3257,8 +3354,8 @@ function createFlippingPageGeometry(surface, direction = 1) {
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
||||
geometry.clearGroups();
|
||||
geometry.addGroup(0, topIndices.length, 0);
|
||||
geometry.addGroup(topIndices.length, bottomIndices.length, 1);
|
||||
geometry.addGroup(0, topIndices.length, topMaterialIndex);
|
||||
geometry.addGroup(topIndices.length, bottomIndices.length, bottomMaterialIndex);
|
||||
geometry.addGroup(topIndices.length + bottomIndices.length, wallIndices.length, 2);
|
||||
geometry.computeVertexNormals();
|
||||
return geometry;
|
||||
@@ -3271,7 +3368,7 @@ function createFlippingPageGeometry(surface, direction = 1) {
|
||||
function pageUvForSide(side, u, v) {
|
||||
return {
|
||||
x: side < 0 ? 1 - u : u,
|
||||
y: 1 - v
|
||||
y: v
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3313,7 +3410,10 @@ function finishActiveFlip(flip) {
|
||||
spreadIndex: Math.max(0, Math.round(Number(flip.targetSpread)))
|
||||
};
|
||||
maxVisitedPagePosition = Math.max(maxVisitedPagePosition, getCurrentPagePosition());
|
||||
syncReadingProgressToCurrentPage();
|
||||
syncReadingProgressToCurrentPage({
|
||||
rebuild: 'defer',
|
||||
reason: 'page-flip-finished'
|
||||
});
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent('webgl-book:page-flip-finished', {
|
||||
detail: {
|
||||
|
||||
Reference in New Issue
Block a user