Optimize WebGL book texture reveal

This commit is contained in:
2026-06-06 16:44:15 +02:00
parent 1b8c8f8bce
commit 081cfa9902
7 changed files with 183 additions and 77 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ class BookPageFormatModule extends BaseModule {
topIn: 0.46, topIn: 0.46,
bottomIn: 0.58, bottomIn: 0.58,
innerIn: 0.62, innerIn: 0.62,
outerIn: 0.72 outerIn: 0.86
}), }),
typography: Object.freeze({ typography: Object.freeze({
fontFamily: '"EB Garamond", "EB Garamond 12", serif', fontFamily: '"EB Garamond", "EB Garamond 12", serif',
+37 -4
View File
@@ -15,6 +15,8 @@ class BookPaginationModule extends BaseModule {
this.spreads = []; this.spreads = [];
this.currentSpreadIndex = 0; this.currentSpreadIndex = 0;
this.refreshToken = 0; this.refreshToken = 0;
this.latestBlockId = 0;
this.latestRenderedBlockId = 0;
this.bindMethods([ this.bindMethods([
'initialize', 'initialize',
@@ -24,6 +26,7 @@ class BookPaginationModule extends BaseModule {
'getDropCapText', 'getDropCapText',
'extractDropCapText', 'extractDropCapText',
'extractLines', 'extractLines',
'countLineWords',
'getLineGeometry', 'getLineGeometry',
'getSpread', 'getSpread',
'getCurrentSpread', 'getCurrentSpread',
@@ -57,12 +60,19 @@ class BookPaginationModule extends BaseModule {
); );
if (!gameId || latestBlockId <= 0 || typeof this.storyHistory?.getBlocksRange !== 'function') { if (!gameId || latestBlockId <= 0 || typeof this.storyHistory?.getBlocksRange !== 'function') {
this.spreads = []; this.spreads = [];
this.latestBlockId = 0;
this.latestRenderedBlockId = 0;
this.publish(); this.publish();
return; return;
} }
const blocks = await this.storyHistory.getBlocksRange(gameId, 1, latestBlockId); const blocks = await this.storyHistory.getBlocksRange(gameId, 1, latestBlockId);
if (token !== this.refreshToken) return; if (token !== this.refreshToken) return;
this.latestBlockId = latestBlockId;
this.latestRenderedBlockId = Math.max(
0,
Number(detail.latestRenderedBlockId || this.storyHistory?.latestRenderedBlockId || 0)
);
this.spreads = this.buildSpreads(blocks); this.spreads = this.buildSpreads(blocks);
this.currentSpreadIndex = Math.max(0, Math.min(this.currentSpreadIndex, Math.max(0, this.spreads.length - 1))); this.currentSpreadIndex = Math.max(0, Math.min(this.currentSpreadIndex, Math.max(0, this.spreads.length - 1)));
this.publish(); this.publish();
@@ -84,7 +94,7 @@ class BookPaginationModule extends BaseModule {
layout.lines.forEach((line, layoutLineIndex) => { layout.lines.forEach((line, layoutLineIndex) => {
const geometry = this.getLineGeometry(cursorLine); const geometry = this.getLineGeometry(cursorLine);
const lineWordCount = line.nodes.filter(node => node?.type === 'box' && node.value).length; const lineWordCount = this.countLineWords(line);
if (!spreads[geometry.spreadIndex]) { if (!spreads[geometry.spreadIndex]) {
spreads[geometry.spreadIndex] = { index: geometry.spreadIndex, left: [], right: [] }; spreads[geometry.spreadIndex] = { index: geometry.spreadIndex, left: [], right: [] };
} }
@@ -100,7 +110,8 @@ class BookPaginationModule extends BaseModule {
lineHeightPx: layout.lineHeightPx, lineHeightPx: layout.lineHeightPx,
fontStyle: layout.fontStyle, fontStyle: layout.fontStyle,
blockWordStart: blockWordCursor, blockWordStart: blockWordCursor,
dropCapText: layoutLineIndex === 0 ? layout.dropCapText : '' dropCapText: layoutLineIndex === 0 ? layout.dropCapText : '',
smallCaps: Boolean(layout.dropCap && layoutLineIndex === 0)
}); });
blockWordCursor += lineWordCount; blockWordCursor += lineWordCount;
cursorLine += 1; cursorLine += 1;
@@ -125,7 +136,7 @@ class BookPaginationModule extends BaseModule {
const bottomSpaceLines = role === 'chapter-heading' || role === 'section-heading' ? 1 : 0; const bottomSpaceLines = role === 'chapter-heading' || role === 'section-heading' ? 1 : 0;
const lineHeightPx = Math.max(1, Number(this.metrics.typographyLineHeightPx || 1)); const lineHeightPx = Math.max(1, Number(this.metrics.typographyLineHeightPx || 1));
const fontPx = Math.max(1, Number(this.metrics.bodyFontSizePx || lineHeightPx / 1.5)); const fontPx = Math.max(1, Number(this.metrics.bodyFontSizePx || lineHeightPx / 1.5));
const dropCapWidth = dropCap ? lineHeightPx * 1.58 : 0; const dropCapWidth = dropCap ? lineHeightPx * 1.72 : 0;
const indent = (isHeading || block.isFirstParagraphInChapter || block.metadata?.isFirstParagraphInChapter || block.addTopSpace) const indent = (isHeading || block.isFirstParagraphInChapter || block.metadata?.isFirstParagraphInChapter || block.addTopSpace)
? 0 ? 0
: lineHeightPx * 1.5; : lineHeightPx * 1.5;
@@ -195,12 +206,32 @@ class BookPaginationModule extends BaseModule {
offset, offset,
ratio: breaks[index].ratio || 0, ratio: breaks[index].ratio || 0,
isFinal: index === breaks.length - 1, isFinal: index === breaks.length - 1,
hyphenated: Boolean(lineNodes.at(-1)?.type === 'penalty' && lineNodes.at(-1)?.penalty === 100),
align: options.align || 'justify' align: options.align || 'justify'
}); });
} }
return lines; return lines;
} }
countLineWords(line = {}) {
const nodes = Array.isArray(line.nodes) ? line.nodes : [];
let count = 0;
let previousWasGlue = true;
nodes.forEach((node) => {
if (!node) return;
if (node.type === 'glue') {
previousWasGlue = true;
return;
}
if (node.type === 'penalty') return;
if (node.type === 'box' && node.value) {
if (previousWasGlue) count += 1;
previousWasGlue = false;
}
});
return count;
}
getLineGeometry(globalLine) { getLineGeometry(globalLine) {
const linesPerPage = Math.max(1, Math.floor(this.metrics.content.height / this.metrics.typographyLineHeightPx || 1)); const linesPerPage = Math.max(1, Math.floor(this.metrics.content.height / this.metrics.typographyLineHeightPx || 1));
const spreadLineCount = linesPerPage * 2; const spreadLineCount = linesPerPage * 2;
@@ -233,7 +264,9 @@ class BookPaginationModule extends BaseModule {
detail: { detail: {
spread: this.getCurrentSpread(), spread: this.getCurrentSpread(),
spreadIndex: this.currentSpreadIndex, spreadIndex: this.currentSpreadIndex,
spreadCount: this.spreads.length spreadCount: this.spreads.length,
latestBlockId: this.latestBlockId,
latestRenderedBlockId: this.latestRenderedBlockId
} }
})); }));
} }
+132 -43
View File
@@ -26,6 +26,7 @@ class BookTextureRendererModule extends BaseModule {
}; };
this.currentSpread = null; this.currentSpread = null;
this.activeAnimations = new Map(); this.activeAnimations = new Map();
this.revealedBlockIds = new Set();
this.animationFrameId = null; this.animationFrameId = null;
this.lastAnimationFrameAt = 0; this.lastAnimationFrameAt = 0;
this.targetFrameDurationMs = 1000 / 30; this.targetFrameDurationMs = 1000 / 30;
@@ -39,9 +40,13 @@ class BookTextureRendererModule extends BaseModule {
'drawPageLines', 'drawPageLines',
'drawLine', 'drawLine',
'drawWord', 'drawWord',
'buildLineSegments',
'startRevealAnimation', 'startRevealAnimation',
'fastForwardAnimations', 'fastForwardAnimations',
'stopAnimations', 'stopAnimations',
'getBlockSides',
'getAnimatedSides',
'markPendingReveal',
'requestAnimationFrame', 'requestAnimationFrame',
'tickAnimations', 'tickAnimations',
'publishSpread', 'publishSpread',
@@ -60,6 +65,9 @@ class BookTextureRendererModule extends BaseModule {
this.drawEmptySpread(); this.drawEmptySpread();
this.addEventListener(document, 'webgl-book:scene-ready', this.handleSceneReady); this.addEventListener(document, 'webgl-book:scene-ready', this.handleSceneReady);
this.addEventListener(document, 'book-pagination:spread-updated', (event) => { this.addEventListener(document, 'book-pagination:spread-updated', (event) => {
const latestBlockId = event.detail?.latestBlockId;
const latestRenderedBlockId = Math.max(0, Number(event.detail?.latestRenderedBlockId || 0));
if (latestBlockId && Number(latestBlockId) > latestRenderedBlockId) this.markPendingReveal(latestBlockId);
this.drawSpread(event.detail?.spread || this.pagination?.getCurrentSpread?.()); this.drawSpread(event.detail?.spread || this.pagination?.getCurrentSpread?.());
}); });
this.addEventListener(document, 'book-texture:reveal-block', (event) => { this.addEventListener(document, 'book-texture:reveal-block', (event) => {
@@ -92,13 +100,15 @@ class BookTextureRendererModule extends BaseModule {
this.publishSpread(); this.publishSpread();
} }
drawSpread(spread = null) { drawSpread(spread = null, sides = null) {
this.currentSpread = spread || { left: [], right: [] }; this.currentSpread = spread || { left: [], right: [] };
this.drawPageBase('left'); const sidesToDraw = Array.isArray(sides) && sides.length ? sides : ['left', 'right'];
this.drawPageBase('right'); sidesToDraw.forEach((side) => {
this.drawPageLines('left', this.currentSpread?.left || []); if (!this.canvases[side]) return;
this.drawPageLines('right', this.currentSpread?.right || []); this.drawPageBase(side);
this.publishSpread(); this.drawPageLines(side, this.currentSpread?.[side] || []);
});
this.publishSpread(sidesToDraw);
} }
drawPageBase(side) { drawPageBase(side) {
@@ -158,51 +168,96 @@ class BookTextureRendererModule extends BaseModule {
let x = metrics.content.x + centerOffset; let x = metrics.content.x + centerOffset;
let wordIndex = 0; let wordIndex = 0;
ctx.font = `${fontStyle}${fontPx}px ${metrics.typography.fontFamily}`; ctx.font = `${fontStyle}${lineRecord.smallCaps ? 'small-caps ' : ''}${fontPx}px ${metrics.typography.fontFamily}`;
if (lineRecord.dropCapText) { if (lineRecord.dropCapText) {
ctx.save(); ctx.save();
ctx.font = `${Math.round(lineHeightPx * 2.08)}px "EB Garamond Initials", ${metrics.typography.fontFamily}`; const alpha = this.getWordAlpha(lineRecord, 0);
ctx.textBaseline = 'top'; if (alpha <= 0) {
ctx.fillText( ctx.restore();
String(lineRecord.dropCapText), } else {
metrics.content.x, ctx.globalAlpha *= alpha;
metrics.content.y + (Number(lineRecord.pageLine || 0) * lineHeightPx) - (lineHeightPx * 0.08) ctx.font = `${Math.round(lineHeightPx * 2.14)}px "EB Garamond Initials", ${metrics.typography.fontFamily}`;
); ctx.textBaseline = 'top';
ctx.restore(); ctx.fillText(
ctx.font = `${fontStyle}${fontPx}px ${metrics.typography.fontFamily}`; String(lineRecord.dropCapText),
metrics.content.x,
metrics.content.y + (Number(lineRecord.pageLine || 0) * lineHeightPx) - (lineHeightPx * 0.05)
);
ctx.restore();
}
ctx.font = `${fontStyle}${lineRecord.smallCaps ? 'small-caps ' : ''}${fontPx}px ${metrics.typography.fontFamily}`;
} }
this.buildLineSegments(ctx, nodes, line, ratio).forEach((segment) => {
this.drawWord(ctx, segment.value, x + segment.x, baseY, lineRecord, segment.wordIndex);
});
}
buildLineSegments(ctx, nodes = [], line = {}, ratio = 0) {
const segments = [];
let x = 0;
let currentSegment = null;
let previousWasGlue = true;
nodes.forEach((node, index) => { nodes.forEach((node, index) => {
if (!node) return; if (!node) return;
if (node.type === 'box' && node.value) { if (node.type === 'box' && node.value) {
const nextNode = nodes[index + 1]; const value = String(node.value);
const value = `${node.value}${nextNode?.type === 'penalty' && nextNode.penalty === 100 ? '-' : ''}`; const width = Number(node.width || ctx.measureText(value).width || 0);
this.drawWord(ctx, value, x, baseY, lineRecord, wordIndex); if (currentSegment && !previousWasGlue) {
x += Number(node.width || ctx.measureText(value).width || 0); currentSegment.value += value;
wordIndex += 1; currentSegment.width += width;
} else {
currentSegment = {
value,
x,
width,
wordIndex: segments.length
};
segments.push(currentSegment);
}
x += width;
previousWasGlue = false;
} else if (node.type === 'glue' && node.width !== 0) { } else if (node.type === 'glue' && node.width !== 0) {
let width = Number(node.width || 0); let width = Number(node.width || 0);
if (ratio > 0) width += Number(node.stretch || 0) * ratio; if (ratio > 0) width += Number(node.stretch || 0) * ratio;
if (ratio < 0) width += Number(node.shrink || 0) * ratio; if (ratio < 0) width += Number(node.shrink || 0) * ratio;
x += width; x += width;
previousWasGlue = true;
currentSegment = null;
} else if (node.type === 'penalty' && node.penalty === 100) {
const isLineEndHyphen = Boolean(line.hyphenated && index === nodes.length - 1 && currentSegment);
if (isLineEndHyphen) {
const hyphenWidth = Number(node.width || ctx.measureText('-').width || 0);
currentSegment.value += '-';
currentSegment.width += hyphenWidth;
x += hyphenWidth;
}
previousWasGlue = false;
} }
}); });
return segments;
} }
drawWord(ctx, value, x, baseY, lineRecord, localWordIndex) { drawWord(ctx, value, x, baseY, lineRecord, localWordIndex) {
const alpha = this.getWordAlpha(lineRecord, localWordIndex);
if (alpha <= 0) return;
const previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = previousAlpha * alpha;
ctx.fillText(value, x, baseY);
ctx.globalAlpha = previousAlpha;
}
getWordAlpha(lineRecord, localWordIndex) {
const animation = this.activeAnimations.get(String(lineRecord.blockId ?? '')); const animation = this.activeAnimations.get(String(lineRecord.blockId ?? ''));
if (!animation) { if (!animation) {
ctx.globalAlpha = 1; return 1;
ctx.fillText(value, x, baseY);
return;
} }
const globalWordIndex = Number(lineRecord.blockWordStart || 0) + localWordIndex; const globalWordIndex = Number(lineRecord.blockWordStart || 0) + localWordIndex;
const timing = animation.wordTimings[globalWordIndex]; const timing = animation.wordTimings[globalWordIndex];
if (!timing) { if (!timing) {
ctx.globalAlpha = animation.completed ? 1 : 0; return animation.completed ? 1 : 0;
ctx.fillText(value, x, baseY);
ctx.globalAlpha = 1;
return;
} }
const elapsed = animation.completed const elapsed = animation.completed
@@ -210,12 +265,7 @@ class BookTextureRendererModule extends BaseModule {
: performance.now() - animation.startedAt; : performance.now() - animation.startedAt;
const duration = Math.max(1, Number(timing.duration || 1)); const duration = Math.max(1, Number(timing.duration || 1));
const progress = Math.max(0, Math.min(1, (elapsed - Number(timing.delay || 0)) / duration)); const progress = Math.max(0, Math.min(1, (elapsed - Number(timing.delay || 0)) / duration));
if (progress <= 0) return; return progress;
const previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = previousAlpha * progress;
ctx.fillText(value, x, baseY);
ctx.globalAlpha = previousAlpha;
} }
startRevealAnimation(detail = {}) { startRevealAnimation(detail = {}) {
@@ -227,6 +277,7 @@ class BookTextureRendererModule extends BaseModule {
startedAt: performance.now(), startedAt: performance.now(),
completed: false completed: false
}); });
this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.(), this.getBlockSides(blockId));
this.requestAnimationFrame(); this.requestAnimationFrame();
} }
@@ -235,11 +286,12 @@ class BookTextureRendererModule extends BaseModule {
this.activeAnimations.forEach((animation) => { this.activeAnimations.forEach((animation) => {
if (!animation.completed) { if (!animation.completed) {
animation.completed = true; animation.completed = true;
this.revealedBlockIds.add(String(animation.blockId ?? ''));
changed = true; changed = true;
} }
}); });
if (changed) { if (changed) {
this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.()); this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.(), this.getAnimatedSides(true));
} }
} }
@@ -252,6 +304,39 @@ class BookTextureRendererModule extends BaseModule {
this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.()); this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.());
} }
getBlockSides(blockId) {
const id = String(blockId ?? '');
const spread = this.currentSpread || this.pagination?.getCurrentSpread?.() || { left: [], right: [] };
return ['left', 'right'].filter((side) => {
const lines = Array.isArray(spread?.[side]) ? spread[side] : [];
return lines.some(line => String(line?.blockId ?? '') === id);
});
}
getAnimatedSides(includeCompleted = false) {
const spread = this.currentSpread || this.pagination?.getCurrentSpread?.() || { left: [], right: [] };
const activeBlockIds = new Set();
this.activeAnimations.forEach((animation, blockId) => {
if (includeCompleted || !animation.completed) activeBlockIds.add(String(blockId));
});
const sides = ['left', 'right'].filter((side) => {
const lines = Array.isArray(spread?.[side]) ? spread[side] : [];
return lines.some(line => activeBlockIds.has(String(line?.blockId ?? '')));
});
return sides.length ? sides : ['left', 'right'];
}
markPendingReveal(blockId) {
const id = String(blockId ?? '');
if (!id || this.activeAnimations.has(id) || this.revealedBlockIds.has(id)) return;
this.activeAnimations.set(id, {
blockId,
wordTimings: [],
startedAt: performance.now(),
completed: false
});
}
requestAnimationFrame() { requestAnimationFrame() {
if (this.animationFrameId) return; if (this.animationFrameId) return;
this.animationFrameId = window.setTimeout(() => this.tickAnimations(performance.now()), this.targetFrameDurationMs); this.animationFrameId = window.setTimeout(() => this.tickAnimations(performance.now()), this.targetFrameDurationMs);
@@ -269,26 +354,30 @@ class BookTextureRendererModule extends BaseModule {
const currentNow = performance.now(); const currentNow = performance.now();
this.activeAnimations.forEach((animation) => { this.activeAnimations.forEach((animation) => {
if (animation.completed) return; if (animation.completed) return;
if (!Array.isArray(animation.wordTimings) || animation.wordTimings.length === 0) return;
const lastTiming = animation.wordTimings.at(-1); const lastTiming = animation.wordTimings.at(-1);
const total = Number(lastTiming?.delay || 0) + Number(lastTiming?.duration || 0); const total = Number(lastTiming?.delay || 0) + Number(lastTiming?.duration || 0);
if (currentNow - animation.startedAt >= total + 50) { if (currentNow - animation.startedAt >= total + 50) {
animation.completed = true; animation.completed = true;
this.revealedBlockIds.add(String(animation.blockId ?? ''));
} else { } else {
hasActive = true; hasActive = true;
} }
}); });
this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.()); this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.(), this.getAnimatedSides(true));
if (hasActive) this.requestAnimationFrame(); if (hasActive) this.requestAnimationFrame();
} }
publishSpread() { publishSpread(sides = null) {
const sidesToPublish = Array.isArray(sides) && sides.length ? sides : ['left', 'right'];
const detail = {
metrics: this.metrics,
hitMaps: this.hitMaps
};
if (sidesToPublish.includes('left')) detail.left = this.canvases.left;
if (sidesToPublish.includes('right')) detail.right = this.canvases.right;
document.dispatchEvent(new CustomEvent('webgl-book:page-canvases', { document.dispatchEvent(new CustomEvent('webgl-book:page-canvases', {
detail: { detail
left: this.canvases.left,
right: this.canvases.right,
metrics: this.metrics,
hitMaps: this.hitMaps
}
})); }));
} }
+1 -1
View File
@@ -24,7 +24,7 @@ const ModuleState = {
ERROR: 'ERROR' ERROR: 'ERROR'
}; };
const MODULE_CACHE_BUSTER = '20260606-webgl-texture-dropcap-animation'; const MODULE_CACHE_BUSTER = '20260606-webgl-texture-refresh-fix';
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER; window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
/** /**
+1 -1
View File
@@ -9,7 +9,7 @@ export const PROCEDURAL_BOOK = {
PAGE_WIDTH: 2.24 * 2 / 3, PAGE_WIDTH: 2.24 * 2 / 3,
COVER_DEPTH: 2.30, COVER_DEPTH: 2.30,
OPEN_SEAM_GAP: 0.003, OPEN_SEAM_GAP: 0.003,
PAGE_TEXTURE_FORE_EDGE_INSET_RATIO: 0.075, PAGE_TEXTURE_FORE_EDGE_INSET_RATIO: 0.12,
PROFILE: { PROFILE: {
tableY: 0, tableY: 0,
coverThickness: 0.03, coverThickness: 0.03,
+8 -8
View File
@@ -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 { 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 { 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 { 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=20260606-webgl-texture-dropcap-animation'; import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260606-webgl-texture-refresh-fix';
const canvas = document.getElementById('scene'); const canvas = document.getElementById('scene');
canvas.style.cursor = 'grab'; canvas.style.cursor = 'grab';
@@ -25,7 +25,7 @@ const appInitialState = window.WebGLBookInitialState || {};
const tableDebugName = urlParams.get('tableDebug') || 'none'; const tableDebugName = urlParams.get('tableDebug') || 'none';
const tableDebugMode = tableDebugModes[tableDebugName] ?? tableDebugModes.none; const tableDebugMode = tableDebugModes[tableDebugName] ?? tableDebugModes.none;
const isAppIntegrationMode = appInitialState.appMode === true; const isAppIntegrationMode = appInitialState.appMode === true;
const appRenderPixelRatio = isAppIntegrationMode ? 0.5 : Math.min(window.devicePixelRatio || 1, 2); const appRenderPixelRatio = isAppIntegrationMode ? 1 : Math.min(window.devicePixelRatio || 1, 2);
const labStatus = document.getElementById('lab_status'); const labStatus = document.getElementById('lab_status');
if (labStatus && tableDebugMode !== tableDebugModes.none) { if (labStatus && tableDebugMode !== tableDebugModes.none) {
labStatus.textContent = tableDebugName === 'ao' ? 'scene debug: SSAO' : `table debug: ${tableDebugName}`; labStatus.textContent = tableDebugName === 'ao' ? 'scene debug: SSAO' : `table debug: ${tableDebugName}`;
@@ -40,7 +40,7 @@ renderer.shadowMap.type = THREE.VSMShadowMap;
const generatedTextureCanvases = {}; const generatedTextureCanvases = {};
const maxTextureAnisotropy = renderer.capabilities.getMaxAnisotropy(); const maxTextureAnisotropy = renderer.capabilities.getMaxAnisotropy();
const reflectionPixelRatio = isAppIntegrationMode ? 0.28 : Math.min(window.devicePixelRatio || 1, 2); const reflectionPixelRatio = isAppIntegrationMode ? 0.5 : Math.min(window.devicePixelRatio || 1, 2);
const pageTextureWidth = isAppIntegrationMode ? 1280 : 3200; const pageTextureWidth = isAppIntegrationMode ? 1280 : 3200;
const reflectionTargetSize = new THREE.Vector2(); const reflectionTargetSize = new THREE.Vector2();
const pageRaycaster = new THREE.Raycaster(); const pageRaycaster = new THREE.Raycaster();
@@ -65,8 +65,8 @@ let tableDustTexture = null;
let tableGreaseTexture = null; let tableGreaseTexture = null;
const tableTopY = -0.02; const tableTopY = -0.02;
const bookTableContactClearance = 0.002; const bookTableContactClearance = 0.002;
const tableReflectionBaseWidth = isAppIntegrationMode ? 480 : 4096; const tableReflectionBaseWidth = isAppIntegrationMode ? 640 : 4096;
const tableReflectionBaseHeight = isAppIntegrationMode ? 270 : 2304; const tableReflectionBaseHeight = isAppIntegrationMode ? 360 : 2304;
const tableReflectionTarget = new THREE.WebGLRenderTarget(tableReflectionBaseWidth, tableReflectionBaseHeight, { const tableReflectionTarget = new THREE.WebGLRenderTarget(tableReflectionBaseWidth, tableReflectionBaseHeight, {
colorSpace: THREE.SRGBColorSpace, colorSpace: THREE.SRGBColorSpace,
depthBuffer: true, depthBuffer: true,
@@ -90,7 +90,7 @@ const reflectionUp = new THREE.Vector3();
const candleShadowSources = []; const candleShadowSources = [];
const candleWorldPosition = new THREE.Vector3(); const candleWorldPosition = new THREE.Vector3();
const flameWorldPosition = new THREE.Vector3(); const flameWorldPosition = new THREE.Vector3();
const bookShadowMapSize = isAppIntegrationMode ? 128 : 1536; const bookShadowMapSize = isAppIntegrationMode ? 256 : 1536;
const bookShadowTargets = Array.from({ length: 3 }, () => { const bookShadowTargets = Array.from({ length: 3 }, () => {
const target = new THREE.WebGLRenderTarget(bookShadowMapSize, bookShadowMapSize, { const target = new THREE.WebGLRenderTarget(bookShadowMapSize, bookShadowMapSize, {
colorSpace: THREE.NoColorSpace, colorSpace: THREE.NoColorSpace,
@@ -179,9 +179,9 @@ const rightTexture = new THREE.CanvasTexture(rightCanvas);
[leftTexture, rightTexture].forEach((texture) => { [leftTexture, rightTexture].forEach((texture) => {
texture.colorSpace = THREE.SRGBColorSpace; texture.colorSpace = THREE.SRGBColorSpace;
texture.anisotropy = maxTextureAnisotropy; texture.anisotropy = maxTextureAnisotropy;
texture.minFilter = THREE.LinearMipmapLinearFilter; texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = true; texture.generateMipmaps = false;
}); });
const leatherTextures = createLeatherTextures(); const leatherTextures = createLeatherTextures();
const spineClothTextures = createSpineClothTextures(); const spineClothTextures = createSpineClothTextures();
+3 -19
View File
@@ -484,28 +484,12 @@ class WebGLBookSceneModule extends BaseModule {
handleProcessState(event) { handleProcessState(event) {
const state = event.detail?.state || 'ready'; const state = event.detail?.state || 'ready';
if (state === 'ready' || state === 'paused' || this.mode !== '3d') { this.stopAnimatedTextureRefresh();
this.stopAnimatedTextureRefresh(); if (state === 'ready' || state === 'paused' || this.mode !== '3d') this.triggerTextureRefresh();
this.triggerTextureRefresh();
return;
}
this.startAnimatedTextureRefresh();
} }
startAnimatedTextureRefresh() { startAnimatedTextureRefresh() {
if (this.textureRefreshAnimationId) return; this.stopAnimatedTextureRefresh();
const tick = (now) => {
if (this.mode !== '3d') {
this.textureRefreshAnimationId = null;
return;
}
if (now - this.lastAnimatedTextureRefresh > 100) {
this.lastAnimatedTextureRefresh = now;
window.BookLabDebug?.redrawPageTextures?.();
}
this.textureRefreshAnimationId = window.requestAnimationFrame(tick);
};
this.textureRefreshAnimationId = window.requestAnimationFrame(tick);
} }
stopAnimatedTextureRefresh() { stopAnimatedTextureRefresh() {