Stabilize WebGL flip reveal handoff
This commit is contained in:
@@ -108,10 +108,10 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
await this.timeStage('activate', segment, () => this.activatePreparedSegment(segment, sentence));
|
||||
|
||||
sentence.webglRevealController = () => this.startRevealForSegment(segment);
|
||||
const visualPromise = this.waitForVisualCompletion(segment);
|
||||
const playbackPromise = this.timeStage('playback', segment, () => {
|
||||
return this.playbackCoordinator?.play?.(sentence) || Promise.resolve();
|
||||
});
|
||||
const visualPromise = this.waitForVisualCompletion(segment);
|
||||
await Promise.all([playbackPromise, visualPromise]);
|
||||
|
||||
this.recordDiagnostic('segment-play:end', segment);
|
||||
@@ -172,8 +172,14 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
requiresRightFlip: revealSides.includes('right') && this.requiresRightPageFlipAfterReveal(previewSpread),
|
||||
preparedTexturePlan: texturePlan,
|
||||
preparedAt: performance.now(),
|
||||
revealStartedAt: null,
|
||||
revealStartedPromise: null,
|
||||
resolveRevealStarted: null,
|
||||
status: 'prepared'
|
||||
};
|
||||
segment.revealStartedPromise = new Promise(resolve => {
|
||||
segment.resolveRevealStarted = resolve;
|
||||
});
|
||||
|
||||
this.applyTexturePlan(texturePlan, segment, 'prepare');
|
||||
await this.timeStage('texture-prewarm', segment, () => this.prewarmSegmentTextures(segment));
|
||||
@@ -303,6 +309,11 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
throw new Error('BookPlaybackTimeline: WebGL book lab cannot start prepared reveals explicitly');
|
||||
}
|
||||
window.BookLabDebug.startRevealForBlock(segment.blockId);
|
||||
segment.revealStartedAt = performance.now();
|
||||
if (typeof segment.resolveRevealStarted === 'function') {
|
||||
segment.resolveRevealStarted(segment.revealStartedAt);
|
||||
segment.resolveRevealStarted = null;
|
||||
}
|
||||
this.markBenchmark('reveal-start', segment);
|
||||
this.recordDiagnostic('reveal-started', segment);
|
||||
return true;
|
||||
@@ -337,7 +348,7 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
this.recordDiagnostic('visual-completion:no-right-flip-wait', segment);
|
||||
return;
|
||||
}
|
||||
const committed = await this.timeStage('wait-right-reveal-commit', segment, () => this.waitForRevealCommit(segment));
|
||||
const committed = await this.timeStage('wait-right-reveal-commit', segment, () => this.waitForPlannedRightReveal(segment));
|
||||
if (!committed || this.isChoiceAwaitingPlayer()) return;
|
||||
await this.timeStage('right-page-flip', segment, () => this.requestPageFlip(1, {
|
||||
reason: 'timeline-right-page-filled',
|
||||
@@ -346,6 +357,29 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
}));
|
||||
}
|
||||
|
||||
async waitForPlannedRightReveal(segment = {}) {
|
||||
const startedAt = Number(segment.revealStartedAt)
|
||||
|| await (segment.revealStartedPromise || Promise.resolve(performance.now()));
|
||||
const duration = this.getRightRevealDurationMs(segment);
|
||||
const elapsed = Math.max(0, performance.now() - Number(startedAt || performance.now()));
|
||||
const remaining = Math.max(0, duration - elapsed);
|
||||
const planned = new Promise(resolve => {
|
||||
setTimeout(() => resolve(true), remaining);
|
||||
});
|
||||
return Promise.race([
|
||||
planned,
|
||||
this.waitForRevealCommit(segment)
|
||||
]);
|
||||
}
|
||||
|
||||
getRightRevealDurationMs(segment = {}) {
|
||||
const duration = Number(segment.activeTexturePlan?.reveal?.right?.durationMs
|
||||
?? segment.preparedTexturePlan?.reveal?.right?.durationMs
|
||||
?? 0);
|
||||
if (Number.isFinite(duration) && duration > 0) return duration;
|
||||
return Math.max(1, Number(segment.sentence?.animation?.totalDuration || 1));
|
||||
}
|
||||
|
||||
waitForRevealCommit(segment = {}) {
|
||||
const blockId = String(segment.blockId ?? '');
|
||||
if (!blockId) return Promise.resolve(false);
|
||||
|
||||
@@ -148,6 +148,9 @@ class BookTextureRendererModule extends BaseModule {
|
||||
this.drawSpread(this.currentSpread);
|
||||
});
|
||||
this.addEventListener(document, 'book-texture:fast-forward', this.fastForwardAnimations);
|
||||
this.addEventListener(document, 'webgl-book:reveal-committed', (event) => {
|
||||
this.completeRevealBlockIds(event.detail?.blockIds || []);
|
||||
});
|
||||
this.addEventListener(document, 'ui:command', (event) => {
|
||||
if (event.detail?.type === 'continue') this.fastForwardAnimations();
|
||||
});
|
||||
@@ -1081,6 +1084,18 @@ class BookTextureRendererModule extends BaseModule {
|
||||
}
|
||||
}
|
||||
|
||||
completeRevealBlockIds(blockIds = []) {
|
||||
const ids = Array.isArray(blockIds) ? blockIds : [];
|
||||
ids.forEach((blockId) => {
|
||||
const id = String(blockId ?? '');
|
||||
if (!id) return;
|
||||
const animation = this.activeAnimations.get(id);
|
||||
if (animation) animation.completed = true;
|
||||
this.revealedBlockIds.add(id);
|
||||
this.pendingRevealBlockIds.delete(id);
|
||||
});
|
||||
}
|
||||
|
||||
stopAnimations() {
|
||||
this.activeAnimations.clear();
|
||||
this.pendingRevealBlockIds.clear();
|
||||
|
||||
@@ -76,7 +76,7 @@ class GameLoopModule extends BaseModule {
|
||||
return true;
|
||||
}
|
||||
|
||||
start() {
|
||||
async start() {
|
||||
console.log("GameLoop: Starting game sequence...");
|
||||
|
||||
try {
|
||||
@@ -87,12 +87,14 @@ class GameLoopModule extends BaseModule {
|
||||
console.log("GameLoop: Setting up socket listeners and connecting...");
|
||||
|
||||
// Set up socket event listeners and connect
|
||||
this.setupSocketEventListeners();
|
||||
const connected = await this.setupSocketEventListeners();
|
||||
|
||||
// Set the game loop as running
|
||||
this.isRunning = true;
|
||||
return connected;
|
||||
} catch (error) {
|
||||
console.error("Error starting game loop:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ class GameLoopModule extends BaseModule {
|
||||
|
||||
if (!socketClient) {
|
||||
console.error("Socket client module not found");
|
||||
return;
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
// Connect UI controller to socket client for command handling
|
||||
@@ -181,12 +183,13 @@ class GameLoopModule extends BaseModule {
|
||||
});
|
||||
|
||||
// Connect to the socket server
|
||||
socketClient.connect().then(success => {
|
||||
return socketClient.connect().then(success => {
|
||||
if (success) {
|
||||
console.log("GameLoop: Socket connection established successfully.");
|
||||
} else {
|
||||
console.error("GameLoop: Failed to connect to socket server");
|
||||
}
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -24,7 +24,7 @@ const ModuleState = {
|
||||
ERROR: 'ERROR'
|
||||
};
|
||||
|
||||
const MODULE_CACHE_BUSTER = '20260610-book-timeline-j';
|
||||
const MODULE_CACHE_BUSTER = '20260610-book-timeline-l';
|
||||
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
||||
|
||||
/**
|
||||
@@ -830,17 +830,17 @@ const ModuleLoader = (function() {
|
||||
async function completeFinalization() {
|
||||
isLoadingComplete = true;
|
||||
|
||||
// Call the start method on the game loop module directly
|
||||
// Ensure the game loop module was found during initialization
|
||||
// Call the start method on the game loop module directly.
|
||||
// Starting before hiding the overlay lets socket connection and
|
||||
// save/resume state settle as part of the loader handoff.
|
||||
if (gameLoopModule && typeof gameLoopModule.start === 'function') {
|
||||
// Hide the overlay first, then start the game loop
|
||||
await hideOverlay();
|
||||
console.log("Loader: Overlay hidden, starting Game Loop.");
|
||||
try {
|
||||
gameLoopModule.start();
|
||||
console.log("Loader: Starting Game Loop before hiding overlay.");
|
||||
await gameLoopModule.start();
|
||||
} catch (error) {
|
||||
console.error("Error starting Game Loop:", error);
|
||||
}
|
||||
await hideOverlay();
|
||||
} else {
|
||||
console.error("Loader: Game Loop module not found or start method missing.");
|
||||
// Hide overlay anyway, but log error
|
||||
|
||||
+111
-25
@@ -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-j';
|
||||
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260610-book-timeline-l';
|
||||
|
||||
const canvas = document.getElementById('scene');
|
||||
canvas.style.cursor = 'grab';
|
||||
@@ -430,6 +430,12 @@ materials.flipPageEdge.map = paperTextures.edge;
|
||||
materials.flipPageEdge.normalMap = paperTextures.normal;
|
||||
materials.flipPageEdge.roughnessMap = paperTextures.roughness;
|
||||
materials.flipPageEdge.side = THREE.DoubleSide;
|
||||
materials.flipPageSurface.userData.bookPageReveal = {
|
||||
side: 'flipFront'
|
||||
};
|
||||
materials.flipPageBackSurface.userData.bookPageReveal = {
|
||||
side: 'flipBack'
|
||||
};
|
||||
materials.leftPage.userData.bookPageReveal = {
|
||||
side: 'left'
|
||||
};
|
||||
@@ -441,6 +447,8 @@ materials.headband.userData.isHeadband = true;
|
||||
configureHardcoverPaperMaterial(materials.pageBlock);
|
||||
configureHardcoverPaperMaterial(materials.pageEdge, { useEdgeMap: true });
|
||||
configureHardcoverPaperMaterial(materials.pageSurface);
|
||||
configureHardcoverPaperMaterial(materials.flipPageSurface);
|
||||
configureHardcoverPaperMaterial(materials.flipPageBackSurface);
|
||||
configureHardcoverPaperMaterial(materials.leftPage);
|
||||
configureHardcoverPaperMaterial(materials.rightPage);
|
||||
|
||||
@@ -2072,30 +2080,36 @@ function syncBottomNavigation() {
|
||||
|
||||
function handlePageTextureRecords(event) {
|
||||
const detail = normalizePageTextureRecordDetail(event.detail || {});
|
||||
if (detail.pageMeta) {
|
||||
currentPageMeta = normalizePageMetaPair(detail.pageMeta, currentPageMeta);
|
||||
const incomingPageMeta = detail.pageMeta
|
||||
? normalizePageMetaPair(detail.pageMeta, currentPageMeta)
|
||||
: currentPageMeta;
|
||||
const effectivePageMeta = detail.phase === 'prepare'
|
||||
? incomingPageMeta
|
||||
: incomingPageMeta;
|
||||
if (detail.phase !== 'prepare' && detail.pageMeta) {
|
||||
currentPageMeta = incomingPageMeta;
|
||||
}
|
||||
markPageTextureTiming('handlePageTextureRecords:start', {
|
||||
hasLeft: Boolean(detail.left),
|
||||
hasRight: Boolean(detail.right),
|
||||
revealSides: Object.keys(detail.reveal || {}),
|
||||
phase: detail.phase || 'activate',
|
||||
pageMeta: currentPageMeta
|
||||
pageMeta: effectivePageMeta
|
||||
});
|
||||
const leftReveal = attachRevealPageMeta(detail.reveal?.left, currentPageMeta.left || null);
|
||||
const rightReveal = attachRevealPageMeta(detail.reveal?.right, currentPageMeta.right || null);
|
||||
const leftReveal = attachRevealPageMeta(detail.reveal?.left, effectivePageMeta.left || null);
|
||||
const rightReveal = attachRevealPageMeta(detail.reveal?.right, effectivePageMeta.right || null);
|
||||
if (detail.phase === 'prepare') {
|
||||
if (detail.left) {
|
||||
const texture = preloadPageTexture('left', detail.left, leftReveal, currentPageMeta.left);
|
||||
pageTextureStore?.rememberResidentTexture?.(currentPageMeta.left, texture, detail.left, true);
|
||||
} else if (currentPageMeta.left?.kind === 'blank') {
|
||||
pageTextureStore?.rememberResidentTexture?.(currentPageMeta.left, getBlankPageTexture(), null, false);
|
||||
const texture = preloadPageTexture('left', detail.left, leftReveal, effectivePageMeta.left);
|
||||
pageTextureStore?.rememberResidentTexture?.(effectivePageMeta.left, texture, detail.left, true);
|
||||
} else if (effectivePageMeta.left?.kind === 'blank') {
|
||||
pageTextureStore?.rememberResidentTexture?.(effectivePageMeta.left, getBlankPageTexture(), null, false);
|
||||
}
|
||||
if (detail.right) {
|
||||
const texture = preloadPageTexture('right', detail.right, rightReveal, currentPageMeta.right);
|
||||
pageTextureStore?.rememberResidentTexture?.(currentPageMeta.right, texture, detail.right, true);
|
||||
} else if (currentPageMeta.right?.kind === 'blank') {
|
||||
pageTextureStore?.rememberResidentTexture?.(currentPageMeta.right, getBlankPageTexture(), null, false);
|
||||
const texture = preloadPageTexture('right', detail.right, rightReveal, effectivePageMeta.right);
|
||||
pageTextureStore?.rememberResidentTexture?.(effectivePageMeta.right, texture, detail.right, true);
|
||||
} else if (effectivePageMeta.right?.kind === 'blank') {
|
||||
pageTextureStore?.rememberResidentTexture?.(effectivePageMeta.right, getBlankPageTexture(), null, false);
|
||||
}
|
||||
markPageTextureTiming('handlePageTextureRecords:prepare:end');
|
||||
return;
|
||||
@@ -2104,21 +2118,21 @@ function handlePageTextureRecords(event) {
|
||||
if (leftReveal) {
|
||||
beginPageReveal('left', detail.left, leftReveal);
|
||||
} else {
|
||||
uploadPageTextureDirect('left', detail.left, currentPageMeta.left);
|
||||
uploadPageTextureDirect('left', detail.left, effectivePageMeta.left);
|
||||
}
|
||||
}
|
||||
if (detail.right) {
|
||||
if (rightReveal) {
|
||||
beginPageReveal('right', detail.right, rightReveal);
|
||||
} else {
|
||||
uploadPageTextureDirect('right', detail.right, currentPageMeta.right);
|
||||
uploadPageTextureDirect('right', detail.right, effectivePageMeta.right);
|
||||
}
|
||||
}
|
||||
if (!detail.left && currentPageMeta.left?.kind === 'blank') {
|
||||
applyExplicitBlankPageTexture('left', currentPageMeta.left, 'page-texture-records');
|
||||
if (!detail.left && effectivePageMeta.left?.kind === 'blank') {
|
||||
applyExplicitBlankPageTexture('left', effectivePageMeta.left, 'page-texture-records');
|
||||
}
|
||||
if (!detail.right && currentPageMeta.right?.kind === 'blank') {
|
||||
applyExplicitBlankPageTexture('right', currentPageMeta.right, 'page-texture-records');
|
||||
if (!detail.right && effectivePageMeta.right?.kind === 'blank') {
|
||||
applyExplicitBlankPageTexture('right', effectivePageMeta.right, 'page-texture-records');
|
||||
}
|
||||
markStaticSceneBuffersDirty();
|
||||
document.documentElement.dataset.webglPageTextureMetrics = JSON.stringify({
|
||||
@@ -2427,6 +2441,7 @@ function beginPageReveal(side, sourceCanvas, revealDetail = {}) {
|
||||
});
|
||||
if (prepared?.texture) {
|
||||
material.map = prepared.texture;
|
||||
material.needsUpdate = true;
|
||||
} else {
|
||||
if (material.map !== texture) {
|
||||
material.map = texture;
|
||||
@@ -2549,10 +2564,44 @@ function applyPageRevealRegions(shader, regions = []) {
|
||||
}
|
||||
|
||||
function getPageRevealShader(side) {
|
||||
const material = side === 'left' ? materials.leftPage : materials.rightPage;
|
||||
const material = side === 'left'
|
||||
? materials.leftPage
|
||||
: side === 'right'
|
||||
? materials.rightPage
|
||||
: side === 'flipFront'
|
||||
? materials.flipPageSurface
|
||||
: side === 'flipBack'
|
||||
? materials.flipPageBackSurface
|
||||
: null;
|
||||
return material?.userData?.bookRevealShader || null;
|
||||
}
|
||||
|
||||
function syncFlipRevealShaderFromSource(sourceSide, targetMaterial = materials.flipPageSurface) {
|
||||
if (!sourceSide || !targetMaterial?.userData) return false;
|
||||
const sourceState = pageRevealState[sourceSide];
|
||||
const sourceShader = getPageRevealShader(sourceSide);
|
||||
const targetShader = targetMaterial.userData.bookRevealShader || null;
|
||||
if (!sourceState || !sourceShader?.uniforms || !targetShader?.uniforms) return false;
|
||||
const sourceUniforms = sourceShader.uniforms;
|
||||
const targetUniforms = targetShader.uniforms;
|
||||
targetUniforms.bookRevealActive.value = sourceUniforms.bookRevealActive?.value || 0;
|
||||
targetUniforms.bookRevealElapsedMs.value = sourceUniforms.bookRevealElapsedMs?.value || sourceState.visualElapsedMs || 0;
|
||||
targetUniforms.bookRevealRegionCount.value = sourceUniforms.bookRevealRegionCount?.value || 0;
|
||||
if (targetUniforms.bookRevealBaseMap) targetUniforms.bookRevealBaseMap.value = sourceUniforms.bookRevealBaseMap?.value || sourceState.baseTexture || targetMaterial.map;
|
||||
if (targetUniforms.bookRevealUseBaseMap) targetUniforms.bookRevealUseBaseMap.value = sourceUniforms.bookRevealUseBaseMap?.value || 0;
|
||||
const sourceRects = sourceUniforms.bookRevealRegionRects?.value || [];
|
||||
const targetRects = targetUniforms.bookRevealRegionRects?.value || [];
|
||||
const sourceTimings = sourceUniforms.bookRevealRegionTimings?.value || [];
|
||||
const targetTimings = targetUniforms.bookRevealRegionTimings?.value || [];
|
||||
for (let index = 0; index < Math.min(sourceRects.length, targetRects.length); index += 1) {
|
||||
targetRects[index].copy(sourceRects[index]);
|
||||
}
|
||||
for (let index = 0; index < Math.min(sourceTimings.length, targetTimings.length); index += 1) {
|
||||
targetTimings[index].copy(sourceTimings[index]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getRevealDebugState() {
|
||||
return ['left', 'right'].reduce((state, side) => {
|
||||
const shader = getPageRevealShader(side);
|
||||
@@ -2699,6 +2748,12 @@ function updatePageRevealAnimations(now) {
|
||||
}
|
||||
const progress = THREE.MathUtils.clamp(state.visualElapsedMs / state.durationMs, 0, 1);
|
||||
shader.uniforms.bookRevealElapsedMs.value = state.visualElapsedMs;
|
||||
if (materials.flipPageSurface.userData.sourceRevealSide === side) {
|
||||
syncFlipRevealShaderFromSource(side, materials.flipPageSurface);
|
||||
}
|
||||
if (materials.flipPageBackSurface.userData.sourceRevealSide === side) {
|
||||
syncFlipRevealShaderFromSource(side, materials.flipPageBackSurface);
|
||||
}
|
||||
if (progress < 1) return;
|
||||
|
||||
clearPageReveal(side, 'duration-complete');
|
||||
@@ -2913,8 +2968,8 @@ function startFastPageFlipPrepared(direction, options = {}) {
|
||||
function createPageFlip(direction, startTime, duration) {
|
||||
const sourceSide = direction > 0 ? 1 : -1;
|
||||
const sourcePageSide = direction > 0 ? 'right' : 'left';
|
||||
const sourceLine = topVisibleLine(sourceSide);
|
||||
const destinationLine = topVisibleLine(-sourceSide);
|
||||
const sourceLine = normalizeFlipLineToVisiblePage(topVisibleLine(sourceSide), sourceSide);
|
||||
const destinationLine = normalizeFlipLineToVisiblePage(topVisibleLine(-sourceSide), -sourceSide);
|
||||
if (!sourceLine || !destinationLine) return null;
|
||||
return {
|
||||
direction,
|
||||
@@ -2930,6 +2985,29 @@ function createPageFlip(direction, startTime, duration) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeFlipLineToVisiblePage(line, side) {
|
||||
if (!line || !currentProceduralBookModel) return line;
|
||||
const points = Array.isArray(line.points) ? line.points : [];
|
||||
if (points.length < 2) return line;
|
||||
const pageStartX = side * Math.max(0, Number(currentProceduralBookModel.spineHalf || 0));
|
||||
const endpoint = points[points.length - 1];
|
||||
const sourceStart = points[0];
|
||||
const sourceSpan = Math.max(0.0001, side * (endpoint.x - sourceStart.x));
|
||||
const normalizedPoints = points.map((point) => {
|
||||
const u = THREE.MathUtils.clamp(side * (point.x - sourceStart.x) / sourceSpan, 0, 1);
|
||||
return {
|
||||
x: THREE.MathUtils.lerp(pageStartX, endpoint.x, u),
|
||||
y: point.y
|
||||
};
|
||||
});
|
||||
return {
|
||||
...line,
|
||||
anchor: normalizedPoints[0],
|
||||
points: normalizedPoints,
|
||||
endpoint: normalizedPoints[normalizedPoints.length - 1]
|
||||
};
|
||||
}
|
||||
|
||||
function prepareStaticPageForFlip(flip, prewarm = null) {
|
||||
if (!flip) return false;
|
||||
const sourceSide = flip.direction > 0 ? 'right' : 'left';
|
||||
@@ -2962,6 +3040,8 @@ function prepareStaticPageForFlip(flip, prewarm = null) {
|
||||
}
|
||||
materials.flipPageSurface.map = sourceTexture;
|
||||
materials.flipPageBackSurface.map = backTexture || getBlankPageTexture();
|
||||
materials.flipPageSurface.userData.sourceRevealSide = pageRevealState[sourceSide] ? sourceSide : null;
|
||||
materials.flipPageBackSurface.userData.sourceRevealSide = null;
|
||||
materials.flipPageSurface.normalMap = materials.pageSurface.normalMap;
|
||||
materials.flipPageBackSurface.normalMap = materials.pageSurface.normalMap;
|
||||
materials.flipPageSurface.roughnessMap = materials.pageSurface.roughnessMap;
|
||||
@@ -3006,15 +3086,17 @@ function prepareStaticPageForFlip(flip, prewarm = null) {
|
||||
...lastFlipTexturePreflight,
|
||||
usedResidentBackTexture: Boolean(backTexture && backTexture !== getBlankPageTexture())
|
||||
});
|
||||
syncFlipRevealShaderFromSource(sourceSide, materials.flipPageSurface);
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveCurrentFlipSourceTexture(side) {
|
||||
const pageMeta = currentPageMeta?.[side] || null;
|
||||
if (pageMeta?.kind === 'blank') return getBlankPageTexture();
|
||||
const material = side === 'left' ? materials.leftPage : materials.rightPage;
|
||||
if (pageRevealState[side]) return material?.map || null;
|
||||
const resident = pageTextureStore?.getResidentTextureForMeta?.(pageMeta);
|
||||
if (resident) return resident;
|
||||
const material = side === 'left' ? materials.leftPage : materials.rightPage;
|
||||
return material?.map || null;
|
||||
}
|
||||
|
||||
@@ -3366,8 +3448,10 @@ function createFlippingPageGeometry(surface, direction = 1) {
|
||||
}
|
||||
|
||||
function pageUvForSide(side, u, v) {
|
||||
const inset = THREE.MathUtils.clamp(Number(PROCEDURAL_BOOK.PAGE_TEXTURE_FORE_EDGE_INSET_RATIO || 0), 0, 0.35);
|
||||
const pageU = THREE.MathUtils.clamp(u / Math.max(0.0001, 1 - inset), 0, 1);
|
||||
return {
|
||||
x: side < 0 ? 1 - u : u,
|
||||
x: side < 0 ? 1 - pageU : pageU,
|
||||
y: v
|
||||
};
|
||||
}
|
||||
@@ -3462,6 +3546,8 @@ function removeFlipMesh(flip) {
|
||||
book.remove(flip.mesh);
|
||||
flip.mesh.geometry.dispose();
|
||||
flip.mesh = null;
|
||||
materials.flipPageSurface.userData.sourceRevealSide = null;
|
||||
materials.flipPageBackSurface.userData.sourceRevealSide = null;
|
||||
}
|
||||
|
||||
function easeInOutCubic(t) {
|
||||
|
||||
Reference in New Issue
Block a user