Fix WebGL line reveal renderer
This commit is contained in:
+134
-59
@@ -40,7 +40,7 @@ renderer.shadowMap.type = THREE.VSMShadowMap;
|
||||
|
||||
const generatedTextureCanvases = {};
|
||||
const maxTextureAnisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
const reflectionPixelRatio = 2;
|
||||
const reflectionPixelRatio = 1;
|
||||
const pageTextureWidth = 3072;
|
||||
const reflectionTargetSize = new THREE.Vector2();
|
||||
const pageRaycaster = new THREE.Raycaster();
|
||||
@@ -80,8 +80,8 @@ let tableDustTexture = null;
|
||||
let tableGreaseTexture = null;
|
||||
const tableTopY = -0.02;
|
||||
const bookTableContactClearance = 0.002;
|
||||
const tableReflectionBaseWidth = 4096;
|
||||
const tableReflectionBaseHeight = 2304;
|
||||
const tableReflectionBaseWidth = 2048;
|
||||
const tableReflectionBaseHeight = 1152;
|
||||
const tableReflectionTarget = new THREE.WebGLRenderTarget(tableReflectionBaseWidth, tableReflectionBaseHeight, {
|
||||
colorSpace: THREE.SRGBColorSpace,
|
||||
depthBuffer: true,
|
||||
@@ -210,10 +210,11 @@ const fastFlipCount = 10;
|
||||
const fastFlipOverlap = 5;
|
||||
let activeFlips = [];
|
||||
let pendingPageFlips = 0;
|
||||
const pendingRevealStartBlockIds = new Set();
|
||||
|
||||
const paperColor = new THREE.Color(0xece4ca);
|
||||
const inkColor = '#1a1009';
|
||||
const maxRevealWords = 256;
|
||||
const maxRevealRegions = 128;
|
||||
const completedRevealElapsedMs = 1000000000;
|
||||
|
||||
await reportLabStep(48, 'Preparing high-resolution page textures');
|
||||
@@ -584,7 +585,8 @@ window.BookLabDebug = {
|
||||
maxResidentPageTextures,
|
||||
pageCacheProblemCount: pageCacheProblemLog.length,
|
||||
flipFrontBackShareMaterial: materials.flipPageSurface === materials.flipPageBackSurface,
|
||||
mirrorRefreshesEveryFrame: true
|
||||
mirrorRefreshesEveryFrame: true,
|
||||
mirrorRefreshesWhenStaticDirty: true
|
||||
};
|
||||
},
|
||||
projectPointerToPage(clientX, clientY) {
|
||||
@@ -696,11 +698,11 @@ function buildTable() {
|
||||
tableNormal.wrapT = THREE.RepeatWrapping;
|
||||
tableNormal.repeat.set(2.15, 1.45);
|
||||
tableNormal.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
tableDustTexture = loadUtilityTexture('/assets/webgl/table_dust_4k.png');
|
||||
tableDustTexture = loadUtilityTexture('/assets/webgl/table_dust_4k.png', { maxSize: 2048 });
|
||||
tableDustTexture.wrapS = THREE.ClampToEdgeWrapping;
|
||||
tableDustTexture.wrapT = THREE.ClampToEdgeWrapping;
|
||||
tableDustTexture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
tableGreaseTexture = loadUtilityTexture('/assets/webgl/table_grease_4k.png');
|
||||
tableGreaseTexture = loadUtilityTexture('/assets/webgl/table_grease_4k.png', { maxSize: 2048 });
|
||||
tableGreaseTexture.wrapS = THREE.ClampToEdgeWrapping;
|
||||
tableGreaseTexture.wrapT = THREE.ClampToEdgeWrapping;
|
||||
tableGreaseTexture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
@@ -724,8 +726,21 @@ function buildTable() {
|
||||
scene.add(tableMesh);
|
||||
}
|
||||
|
||||
function loadUtilityTexture(url) {
|
||||
const texture = new THREE.TextureLoader().load(url);
|
||||
function loadUtilityTexture(url, options = {}) {
|
||||
const texture = new THREE.TextureLoader().load(url, (loadedTexture) => {
|
||||
const maxSize = Math.max(0, Number(options.maxSize || 0));
|
||||
const image = loadedTexture.image;
|
||||
const width = Number(image?.naturalWidth || image?.width || 0);
|
||||
const height = Number(image?.naturalHeight || image?.height || 0);
|
||||
if (!maxSize || !width || !height || (width <= maxSize && height <= maxSize)) return;
|
||||
const scale = Math.min(maxSize / width, maxSize / height);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = Math.max(1, Math.round(width * scale));
|
||||
canvas.height = Math.max(1, Math.round(height * scale));
|
||||
canvas.getContext('2d')?.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
loadedTexture.image = canvas;
|
||||
loadedTexture.needsUpdate = true;
|
||||
});
|
||||
texture.colorSpace = THREE.NoColorSpace;
|
||||
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
@@ -738,7 +753,7 @@ function configureBookShadowReceiver(material, strength) {
|
||||
const isHardcoverPaper = material.userData?.isHardcoverPaper === true;
|
||||
const isHeadband = material.userData?.isHeadband === true;
|
||||
const pageReveal = material.userData?.bookPageReveal || null;
|
||||
material.customProgramCacheKey = () => `book-shadow-receiver-${strength.toFixed(2)}-${pageReveal ? 'page-reveal-v2' : isHeadband ? 'headband-v1' : isSpineCloth ? 'spine-cloth-v4' : isHardcoverPaper ? 'hardcover-paper-v1' : 'plain'}`;
|
||||
material.customProgramCacheKey = () => `book-shadow-receiver-${strength.toFixed(2)}-${pageReveal ? 'page-reveal-line-v1' : isHeadband ? 'headband-v1' : isSpineCloth ? 'spine-cloth-v4' : isHardcoverPaper ? 'hardcover-paper-v1' : 'plain'}`;
|
||||
material.onBeforeCompile = (shader) => {
|
||||
shader.uniforms.bookShadowMaps = { value: bookShadowTargets.map((target) => target.texture) };
|
||||
shader.uniforms.bookShadowMatrices = { value: bookShadowMatrices };
|
||||
@@ -748,9 +763,9 @@ function configureBookShadowReceiver(material, strength) {
|
||||
if (pageReveal) {
|
||||
shader.uniforms.bookRevealActive = { value: 0 };
|
||||
shader.uniforms.bookRevealElapsedMs = { value: completedRevealElapsedMs };
|
||||
shader.uniforms.bookRevealWordCount = { value: 0 };
|
||||
shader.uniforms.bookRevealWordRects = { value: Array.from({ length: maxRevealWords }, () => new THREE.Vector4(0, 0, 0, 0)) };
|
||||
shader.uniforms.bookRevealWordTimings = { value: Array.from({ length: maxRevealWords }, () => new THREE.Vector4(0, 1, 0, 0)) };
|
||||
shader.uniforms.bookRevealRegionCount = { value: 0 };
|
||||
shader.uniforms.bookRevealRegionRects = { value: Array.from({ length: maxRevealRegions }, () => new THREE.Vector4(0, 0, 0, 0)) };
|
||||
shader.uniforms.bookRevealRegionTimings = { value: Array.from({ length: maxRevealRegions }, () => new THREE.Vector4(0, 1, 0, 0)) };
|
||||
shader.uniforms.bookRevealPaperColor = { value: paperColor.clone() };
|
||||
shader.uniforms.bookRevealBaseMap = { value: leftTexture };
|
||||
shader.uniforms.bookRevealUseBaseMap = { value: 0 };
|
||||
@@ -793,9 +808,9 @@ function configureBookShadowReceiver(material, strength) {
|
||||
uniform float bookTableTopY;
|
||||
${pageReveal ? `uniform float bookRevealActive;
|
||||
uniform float bookRevealElapsedMs;
|
||||
uniform int bookRevealWordCount;
|
||||
uniform vec4 bookRevealWordRects[256];
|
||||
uniform vec4 bookRevealWordTimings[256];
|
||||
uniform int bookRevealRegionCount;
|
||||
uniform vec4 bookRevealRegionRects[128];
|
||||
uniform vec4 bookRevealRegionTimings[128];
|
||||
uniform vec3 bookRevealPaperColor;
|
||||
uniform sampler2D bookRevealBaseMap;
|
||||
uniform float bookRevealUseBaseMap;
|
||||
@@ -803,17 +818,17 @@ function configureBookShadowReceiver(material, strength) {
|
||||
|
||||
float bookRevealVisibleMask(vec2 uv) {
|
||||
float hidden = 0.0;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (i >= bookRevealWordCount) break;
|
||||
vec4 rect = bookRevealWordRects[i];
|
||||
for (int i = 0; i < 128; i++) {
|
||||
float enabled = step(float(i) + 0.5, float(bookRevealRegionCount));
|
||||
vec4 rect = bookRevealRegionRects[i];
|
||||
vec2 local = (uv - rect.xy) / max(rect.zw, vec2(0.0001));
|
||||
float inside = step(0.0, local.x) * step(0.0, local.y) * step(local.x, 1.0) * step(local.y, 1.0);
|
||||
vec4 timing = bookRevealWordTimings[i];
|
||||
vec4 timing = bookRevealRegionTimings[i];
|
||||
float progress = clamp((bookRevealElapsedMs - timing.x) / max(1.0, timing.y), 0.0, 1.0);
|
||||
float scan = clamp(local.x * 0.88 + (1.0 - local.y) * 0.12, 0.0, 1.0);
|
||||
float scan = clamp(local.x * 0.96 + (1.0 - local.y) * 0.04, 0.0, 1.0);
|
||||
float feather = max(0.0001, bookRevealSoftness);
|
||||
float visible = smoothstep(scan - feather, scan + feather, progress);
|
||||
hidden = max(hidden, inside * (1.0 - visible));
|
||||
hidden = max(hidden, enabled * inside * (1.0 - visible));
|
||||
}
|
||||
return hidden;
|
||||
}` : ''}
|
||||
@@ -2075,6 +2090,21 @@ function preloadPageTexture(side, sourceCanvas, revealDetail = {}) {
|
||||
return texture;
|
||||
}
|
||||
|
||||
function flushPendingRevealStarts() {
|
||||
if (activeFlips.length > 0 || pendingRevealStartBlockIds.size === 0) return;
|
||||
const blockIds = Array.from(pendingRevealStartBlockIds);
|
||||
pendingRevealStartBlockIds.clear();
|
||||
blockIds.forEach(blockId => startPageRevealForBlock(blockId));
|
||||
}
|
||||
|
||||
function setPageFlipActiveFlag() {
|
||||
if (activeFlips.length > 0) {
|
||||
document.documentElement.dataset.webglPageFlipActive = 'true';
|
||||
} else {
|
||||
delete document.documentElement.dataset.webglPageFlipActive;
|
||||
}
|
||||
}
|
||||
|
||||
function recordPageCacheProblem(detail = {}) {
|
||||
const entry = {
|
||||
...detail,
|
||||
@@ -2195,10 +2225,15 @@ async function preloadCachedPageTexture(pageIndex) {
|
||||
|
||||
async function prewarmSpreadTextures(spreadIndex) {
|
||||
const indices = spreadPageIndices(spreadIndex);
|
||||
await Promise.all([
|
||||
const [left, right] = await Promise.all([
|
||||
preloadCachedPageTexture(indices.left),
|
||||
preloadCachedPageTexture(indices.right)
|
||||
]);
|
||||
return {
|
||||
spreadIndex: Math.max(0, Math.round(Number(spreadIndex || 0))),
|
||||
left,
|
||||
right
|
||||
};
|
||||
}
|
||||
|
||||
async function prewarmFlipTextures(direction, targetSpread = null) {
|
||||
@@ -2206,10 +2241,14 @@ 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)));
|
||||
await Promise.all([
|
||||
const [current, next] = await Promise.all([
|
||||
prewarmSpreadTextures(currentSpread),
|
||||
prewarmSpreadTextures(nextSpread)
|
||||
]);
|
||||
return {
|
||||
current,
|
||||
next
|
||||
};
|
||||
}
|
||||
|
||||
function takePreparedPageTexture(side, revealDetail = {}) {
|
||||
@@ -2258,7 +2297,7 @@ function beginPageReveal(side, sourceCanvas, revealDetail = {}) {
|
||||
|
||||
markPageTextureTiming('revealUpload:start', {
|
||||
side,
|
||||
wordCount: Array.isArray(revealDetail.wordRects) ? revealDetail.wordRects.length : 0,
|
||||
regionCount: Array.isArray(revealDetail.lineRects) ? revealDetail.lineRects.length : 0,
|
||||
usedPreparedTexture: Boolean(prepared),
|
||||
usedPreparedBaseTexture: Boolean(prepared?.baseTexture)
|
||||
});
|
||||
@@ -2292,7 +2331,7 @@ function beginPageReveal(side, sourceCanvas, revealDetail = {}) {
|
||||
document.documentElement.dataset.webglRevealDebug = JSON.stringify({
|
||||
side,
|
||||
blockIds: pageRevealState[side].blockIds,
|
||||
wordCount: Array.isArray(revealDetail.wordRects) ? revealDetail.wordRects.length : 0,
|
||||
regionCount: Array.isArray(revealDetail.lineRects) ? revealDetail.lineRects.length : 0,
|
||||
shaderReady: Boolean(shader?.uniforms),
|
||||
started: pageRevealState[side].startedAt != null
|
||||
});
|
||||
@@ -2303,7 +2342,7 @@ function applyPendingPageReveal(side, shader = getPageRevealShader(side)) {
|
||||
const material = side === 'left' ? materials.leftPage : materials.rightPage;
|
||||
const revealDetail = material?.userData?.pendingPageReveal;
|
||||
if (!revealDetail || !shader?.uniforms) return false;
|
||||
applyPageRevealWords(shader, revealDetail.wordRects || []);
|
||||
applyPageRevealRegions(shader, revealDetail.lineRects || []);
|
||||
shader.uniforms.bookRevealActive.value = 1;
|
||||
shader.uniforms.bookRevealElapsedMs.value = 0;
|
||||
const baseTexture = pageRevealState[side]?.baseTexture;
|
||||
@@ -2312,7 +2351,7 @@ function applyPendingPageReveal(side, shader = getPageRevealShader(side)) {
|
||||
document.documentElement.dataset.webglRevealDebug = JSON.stringify({
|
||||
side,
|
||||
blockIds: pageRevealState[side]?.blockIds || revealDetail.blockIds || [],
|
||||
wordCount: Array.isArray(revealDetail.wordRects) ? revealDetail.wordRects.length : 0,
|
||||
regionCount: Array.isArray(revealDetail.lineRects) ? revealDetail.lineRects.length : 0,
|
||||
shaderReady: true,
|
||||
started: pageRevealState[side]?.startedAt != null
|
||||
});
|
||||
@@ -2320,20 +2359,19 @@ function applyPendingPageReveal(side, shader = getPageRevealShader(side)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyPageRevealWords(shader, words = []) {
|
||||
const rectUniforms = shader.uniforms.bookRevealWordRects.value;
|
||||
const timingUniforms = shader.uniforms.bookRevealWordTimings.value;
|
||||
const source = Array.isArray(words) ? words.slice(0, maxRevealWords) : [];
|
||||
shader.uniforms.bookRevealWordCount.value = source.length;
|
||||
source.forEach((word, index) => {
|
||||
const rect = word.rect || {};
|
||||
const timing = word.timing || {};
|
||||
const nextTiming = source[index + 1]?.timing || {};
|
||||
function applyPageRevealRegions(shader, regions = []) {
|
||||
const rectUniforms = shader.uniforms.bookRevealRegionRects.value;
|
||||
const timingUniforms = shader.uniforms.bookRevealRegionTimings.value;
|
||||
const source = Array.isArray(regions) ? regions : [];
|
||||
if (source.length > maxRevealRegions) {
|
||||
throw new Error(`WebGL reveal region count ${source.length} exceeds architectural maximum ${maxRevealRegions}`);
|
||||
}
|
||||
shader.uniforms.bookRevealRegionCount.value = source.length;
|
||||
source.forEach((region, index) => {
|
||||
const rect = region.rect || {};
|
||||
const timing = region.timing || {};
|
||||
const delay = Math.max(0, Number(timing.delay || 0));
|
||||
const nextDelay = Number(nextTiming.delay);
|
||||
const allottedDuration = Number.isFinite(nextDelay) && nextDelay > delay
|
||||
? nextDelay - delay
|
||||
: Number(timing.duration || 1);
|
||||
const duration = Math.max(1, Number(timing.duration || 1));
|
||||
const x = THREE.MathUtils.clamp(Number(rect.x || 0), 0, 1);
|
||||
const y = THREE.MathUtils.clamp(Number(rect.y || 0), 0, 1);
|
||||
const width = THREE.MathUtils.clamp(Number(rect.width || 0), 0, 1);
|
||||
@@ -2346,12 +2384,12 @@ function applyPageRevealWords(shader, words = []) {
|
||||
);
|
||||
timingUniforms[index].set(
|
||||
delay,
|
||||
Math.max(1, allottedDuration),
|
||||
duration,
|
||||
0,
|
||||
0
|
||||
);
|
||||
});
|
||||
for (let index = source.length; index < maxRevealWords; index += 1) {
|
||||
for (let index = source.length; index < maxRevealRegions; index += 1) {
|
||||
rectUniforms[index].set(0, 0, 0, 0);
|
||||
timingUniforms[index].set(0, 1, 0, 0);
|
||||
}
|
||||
@@ -2370,7 +2408,7 @@ function getRevealDebugState() {
|
||||
active: Number(uniforms.bookRevealActive?.value || 0),
|
||||
elapsedMs: Number(uniforms.bookRevealElapsedMs?.value || 0),
|
||||
visualElapsedMs: Number(pageRevealState[side]?.visualElapsedMs || 0),
|
||||
wordCount: Number(uniforms.bookRevealWordCount?.value || 0),
|
||||
regionCount: Number(uniforms.bookRevealRegionCount?.value || 0),
|
||||
usesBaseTexture: Number(uniforms.bookRevealUseBaseMap?.value || 0),
|
||||
fastForwarding: pageRevealState[side]?.fastForwarding === true,
|
||||
started: pageRevealState[side]?.startedAt != null,
|
||||
@@ -2403,7 +2441,7 @@ function clearPageReveal(side, reason = 'clear') {
|
||||
if (shader?.uniforms?.bookRevealActive) {
|
||||
shader.uniforms.bookRevealActive.value = 0;
|
||||
shader.uniforms.bookRevealElapsedMs.value = completedRevealElapsedMs;
|
||||
shader.uniforms.bookRevealWordCount.value = 0;
|
||||
shader.uniforms.bookRevealRegionCount.value = 0;
|
||||
if (shader.uniforms.bookRevealUseBaseMap) shader.uniforms.bookRevealUseBaseMap.value = 0;
|
||||
}
|
||||
previousState?.baseTexture?.dispose?.();
|
||||
@@ -2411,6 +2449,15 @@ function clearPageReveal(side, reason = 'clear') {
|
||||
|
||||
function startPageRevealForBlock(blockId) {
|
||||
const id = String(blockId ?? '');
|
||||
if (!id) return;
|
||||
if (activeFlips.length > 0) {
|
||||
pendingRevealStartBlockIds.add(id);
|
||||
markPageTextureTiming('revealStart:deferred-for-flip', {
|
||||
blockId: id,
|
||||
activeFlips: activeFlips.length
|
||||
});
|
||||
return;
|
||||
}
|
||||
['left', 'right'].forEach((side) => {
|
||||
const state = pageRevealState[side];
|
||||
if (!state || state.startedAt != null) return;
|
||||
@@ -2436,6 +2483,7 @@ function fastForwardPageReveals(blockIds = []) {
|
||||
}
|
||||
|
||||
function updatePageRevealAnimations(now) {
|
||||
if (activeFlips.length > 0) return;
|
||||
['left', 'right'].forEach((side) => {
|
||||
const state = pageRevealState[side];
|
||||
if (!state) return;
|
||||
@@ -2598,10 +2646,11 @@ async function startPageFlip(direction, options = {}) {
|
||||
if (activeFlips.length || !currentProceduralBookModel) return false;
|
||||
if (!options.force && !canPageFlip(direction)) return false;
|
||||
const targetSpread = Number.isFinite(Number(options.targetSpread)) ? Math.max(0, Math.round(Number(options.targetSpread))) : null;
|
||||
await prewarmFlipTextures(direction, targetSpread);
|
||||
const prewarm = await prewarmFlipTextures(direction, targetSpread);
|
||||
return startPageFlipPrepared(direction, {
|
||||
...options,
|
||||
targetSpread
|
||||
targetSpread,
|
||||
prewarm
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2610,12 +2659,15 @@ function startPageFlipPrepared(direction, options = {}) {
|
||||
if (!options.force && !canPageFlip(direction)) return false;
|
||||
const flip = createPageFlip(direction, performance.now(), normalFlipDuration);
|
||||
if (!flip) return false;
|
||||
flip.targetSpread = Number.isFinite(Number(options.targetSpread)) ? Math.max(0, Math.round(Number(options.targetSpread))) : null;
|
||||
if (!prepareStaticPageForFlip(flip, options.prewarm || null)) {
|
||||
return false;
|
||||
}
|
||||
pendingRightPageFlip = false;
|
||||
pendingRightPageFlipAutoplay = false;
|
||||
delete document.documentElement.dataset.webglPendingPageFlip;
|
||||
flip.targetSpread = Number.isFinite(Number(options.targetSpread)) ? Math.max(0, Math.round(Number(options.targetSpread))) : null;
|
||||
prepareStaticPageForFlip(flip);
|
||||
activeFlips.push(flip);
|
||||
setPageFlipActiveFlag();
|
||||
syncBookControls();
|
||||
updateActiveFlips(flip.startTime);
|
||||
return true;
|
||||
@@ -2624,10 +2676,11 @@ function startPageFlipPrepared(direction, options = {}) {
|
||||
async function startFastPageFlip(direction, options = {}) {
|
||||
if (activeFlips.length || !currentProceduralBookModel || !canPageFlip(direction)) return false;
|
||||
const targetSpread = Number.isFinite(Number(options.targetSpread)) ? Math.max(0, Math.round(Number(options.targetSpread))) : null;
|
||||
await prewarmFlipTextures(direction, targetSpread);
|
||||
const prewarm = await prewarmFlipTextures(direction, targetSpread);
|
||||
return startFastPageFlipPrepared(direction, {
|
||||
...options,
|
||||
targetSpread
|
||||
targetSpread,
|
||||
prewarm
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2635,7 +2688,7 @@ function startFastPageFlipPrepared(direction, options = {}) {
|
||||
if (activeFlips.length || !currentProceduralBookModel || !canPageFlip(direction)) return false;
|
||||
const firstFlip = createPageFlip(direction, performance.now(), fastFlipDuration);
|
||||
if (!firstFlip) return false;
|
||||
prepareStaticPageForFlip(firstFlip);
|
||||
if (!prepareStaticPageForFlip(firstFlip, options.prewarm || null)) return false;
|
||||
const startTime = firstFlip.startTime;
|
||||
const interval = fastFlipDuration / fastFlipOverlap;
|
||||
const skippedSpreads = Math.max(2, Number(options.skippedSpreads || fastFlipCount));
|
||||
@@ -2651,6 +2704,7 @@ function startFastPageFlipPrepared(direction, options = {}) {
|
||||
countAsPending: false
|
||||
});
|
||||
}
|
||||
setPageFlipActiveFlag();
|
||||
syncBookControls();
|
||||
updateActiveFlips(startTime);
|
||||
return true;
|
||||
@@ -2676,8 +2730,8 @@ function createPageFlip(direction, startTime, duration) {
|
||||
};
|
||||
}
|
||||
|
||||
function prepareStaticPageForFlip(flip) {
|
||||
if (!flip) return;
|
||||
function prepareStaticPageForFlip(flip, prewarm = null) {
|
||||
if (!flip) return false;
|
||||
const sourceMaterial = flip.sourcePageSide === 'left' ? materials.leftPage : materials.rightPage;
|
||||
const sourceTexture = sourceMaterial?.map || (flip.sourcePageSide === 'left' ? leftTexture : rightTexture);
|
||||
const targetSpread = Number.isFinite(Number(flip.targetSpread))
|
||||
@@ -2685,7 +2739,20 @@ function prepareStaticPageForFlip(flip) {
|
||||
: Math.max(0, Math.round(Number(bookPaginationState.spreadIndex || 0)) + Math.sign(Number(flip.direction || 0)));
|
||||
const targetPages = spreadPageIndices(targetSpread);
|
||||
const targetBackPageIndex = flip.direction > 0 ? targetPages.left : targetPages.right;
|
||||
const backTexture = getResidentPageTexture(targetBackPageIndex) || getBlankPageTexture();
|
||||
const residentBackTexture = getResidentPageTexture(targetBackPageIndex);
|
||||
const requiresWrittenTexture = targetBackPageIndex <= Math.max(2, Number(bookPaginationState.writtenPageLimit || 0));
|
||||
if (!residentBackTexture && requiresWrittenTexture) {
|
||||
recordPageCacheProblem({
|
||||
type: 'flip-back-texture-missing',
|
||||
targetBackPageIndex,
|
||||
targetSpread,
|
||||
direction: flip.direction,
|
||||
prewarmedCurrent: Boolean(prewarm?.current),
|
||||
prewarmedNext: Boolean(prewarm?.next)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
const backTexture = residentBackTexture || getBlankPageTexture();
|
||||
materials.flipPageSurface.map = sourceTexture;
|
||||
materials.flipPageBackSurface.map = backTexture;
|
||||
materials.flipPageSurface.normalMap = materials.pageSurface.normalMap;
|
||||
@@ -2712,6 +2779,14 @@ function prepareStaticPageForFlip(flip) {
|
||||
materials.leftPage.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
markPageTextureTiming('flipTexturePreflight:ready', {
|
||||
direction: flip.direction,
|
||||
sourceSide: flip.sourcePageSide,
|
||||
targetSpread,
|
||||
targetBackPageIndex,
|
||||
usedResidentBackTexture: Boolean(residentBackTexture)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function canPageFlip(direction) {
|
||||
@@ -3013,6 +3088,7 @@ function createFlippingPageGeometry(surface) {
|
||||
function finishActiveFlip(flip) {
|
||||
removeFlipMesh(flip);
|
||||
activeFlips = activeFlips.filter((active) => active !== flip);
|
||||
setPageFlipActiveFlag();
|
||||
if (activeFlips.length === 0 && Number.isFinite(Number(flip.targetSpread))) {
|
||||
bookPaginationState = {
|
||||
...bookPaginationState,
|
||||
@@ -3027,6 +3103,7 @@ function finishActiveFlip(flip) {
|
||||
targetSpread: Number.isFinite(Number(flip.targetSpread)) ? Math.max(0, Math.round(Number(flip.targetSpread))) : null
|
||||
}
|
||||
}));
|
||||
flushPendingRevealStarts();
|
||||
if (flip.commitBundleOnFinish) {
|
||||
if (Number.isFinite(Number(flip.targetSpread))) {
|
||||
syncBookControls();
|
||||
@@ -4119,10 +4196,8 @@ function animate(now = performance.now()) {
|
||||
updateBookShadowMaps();
|
||||
lastFrameTiming.shadows = performance.now() - shadowStartedAt;
|
||||
const reflectionStartedAt = performance.now();
|
||||
const refreshStaticSceneBuffers = true;
|
||||
if (refreshStaticSceneBuffers) {
|
||||
updateTableReflection();
|
||||
}
|
||||
const refreshStaticSceneBuffers = staticSceneBuffersDirty || activeFlips.length > 0;
|
||||
updateTableReflection();
|
||||
lastFrameTiming.reflection = performance.now() - reflectionStartedAt;
|
||||
const renderStartedAt = performance.now();
|
||||
if (tableDebugMode === tableDebugModes.mirror) {
|
||||
|
||||
Reference in New Issue
Block a user