Stage WebGL scene loading
This commit is contained in:
+1
-1
@@ -280,6 +280,6 @@
|
|||||||
console.log(message);
|
console.log(message);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="/js/loader.js?v=20260607-webgl-physical-stack-quality"></script>
|
<script type="module" src="/js/loader.js?v=20260607-webgl-loader-quality-fix"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
* Defines the canonical page geometry used by the WebGL book renderer.
|
* Defines the canonical page geometry used by the WebGL book renderer.
|
||||||
*/
|
*/
|
||||||
import { BaseModule } from './base-module.js';
|
import { BaseModule } from './base-module.js';
|
||||||
import { calculateProceduralBookThickness, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-physical-stack-quality';
|
import { calculateProceduralBookThickness, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-loader-quality-fix';
|
||||||
|
|
||||||
|
export const BOOK_TEXTURE_WIDTH = 3072;
|
||||||
|
|
||||||
class BookPageFormatModule extends BaseModule {
|
class BookPageFormatModule extends BaseModule {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -39,6 +41,7 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
this.bindMethods([
|
this.bindMethods([
|
||||||
'getFormat',
|
'getFormat',
|
||||||
'getAspectRatio',
|
'getAspectRatio',
|
||||||
|
'getTextureWidth',
|
||||||
'getTextureMetrics',
|
'getTextureMetrics',
|
||||||
'setPageCount',
|
'setPageCount',
|
||||||
'getPageCount',
|
'getPageCount',
|
||||||
@@ -67,6 +70,10 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
return this.format.trim.widthIn / this.format.trim.heightIn;
|
return this.format.trim.widthIn / this.format.trim.heightIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTextureWidth() {
|
||||||
|
return BOOK_TEXTURE_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
inchesToTexture(valueIn, textureHeight) {
|
inchesToTexture(valueIn, textureHeight) {
|
||||||
return (Number(valueIn) / this.format.trim.heightIn) * textureHeight;
|
return (Number(valueIn) / this.format.trim.heightIn) * textureHeight;
|
||||||
}
|
}
|
||||||
@@ -105,7 +112,7 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getTextureMetrics(textureWidth = 1280, pageCount = this.pageCount) {
|
getTextureMetrics(textureWidth = BOOK_TEXTURE_WIDTH, pageCount = this.pageCount) {
|
||||||
const width = Math.max(1, Math.round(Number(textureWidth) || 1280));
|
const width = Math.max(1, Math.round(Number(textureWidth) || 1280));
|
||||||
const height = Math.round(width / this.getAspectRatio());
|
const height = Math.round(width / this.getAspectRatio());
|
||||||
const dynamicMargins = this.getDynamicMargins(pageCount);
|
const dynamicMargins = this.getDynamicMargins(pageCount);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class BookPaginationModule extends BaseModule {
|
|||||||
'getSpread',
|
'getSpread',
|
||||||
'getCurrentSpread',
|
'getCurrentSpread',
|
||||||
'setCurrentSpread',
|
'setCurrentSpread',
|
||||||
|
'handlePageCountChanged',
|
||||||
'publish'
|
'publish'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -44,9 +45,10 @@ class BookPaginationModule extends BaseModule {
|
|||||||
this.pageFormat = this.getModule('book-page-format');
|
this.pageFormat = this.getModule('book-page-format');
|
||||||
this.paragraphLayout = this.getModule('paragraph-layout');
|
this.paragraphLayout = this.getModule('paragraph-layout');
|
||||||
this.storyHistory = this.getModule('story-history');
|
this.storyHistory = this.getModule('story-history');
|
||||||
this.metrics = this.pageFormat.getTextureMetrics(1280);
|
this.metrics = this.pageFormat.getTextureMetrics(this.pageFormat.getTextureWidth?.());
|
||||||
|
|
||||||
this.reportProgress(35, 'Preparing book pagination metrics');
|
this.reportProgress(35, 'Preparing book pagination metrics');
|
||||||
|
this.addEventListener(document, 'webgl-book:page-count-changed', this.handlePageCountChanged);
|
||||||
this.addEventListener(document, 'story:history-updated', this.refreshFromHistory);
|
this.addEventListener(document, 'story:history-updated', this.refreshFromHistory);
|
||||||
this.addEventListener(document, 'book-pagination:set-spread', (event) => {
|
this.addEventListener(document, 'book-pagination:set-spread', (event) => {
|
||||||
this.setCurrentSpread(event.detail?.spreadIndex);
|
this.setCurrentSpread(event.detail?.spreadIndex);
|
||||||
@@ -55,6 +57,12 @@ class BookPaginationModule extends BaseModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePageCountChanged(event) {
|
||||||
|
this.pageFormat?.setPageCount?.(event.detail?.pageCount);
|
||||||
|
this.metrics = this.pageFormat.getTextureMetrics(this.pageFormat.getTextureWidth?.());
|
||||||
|
this.refreshFromHistory();
|
||||||
|
}
|
||||||
|
|
||||||
async refreshFromHistory(event = null) {
|
async refreshFromHistory(event = null) {
|
||||||
const token = ++this.refreshToken;
|
const token = ++this.refreshToken;
|
||||||
const detail = event?.detail || {};
|
const detail = event?.detail || {};
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
createPageCanvases(textureWidth = 3072) {
|
createPageCanvases(textureWidth = this.pageFormat?.getTextureWidth?.() || 3072) {
|
||||||
this.metrics = this.pageFormat.getTextureMetrics(textureWidth);
|
this.metrics = this.pageFormat.getTextureMetrics(textureWidth);
|
||||||
['left', 'right'].forEach((side) => {
|
['left', 'right'].forEach((side) => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ const ModuleState = {
|
|||||||
ERROR: 'ERROR'
|
ERROR: 'ERROR'
|
||||||
};
|
};
|
||||||
|
|
||||||
const MODULE_CACHE_BUSTER = '20260607-webgl-physical-stack-quality';
|
const MODULE_CACHE_BUSTER = '20260607-webgl-loader-quality-fix';
|
||||||
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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=20260607-webgl-physical-stack-quality';
|
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-loader-quality-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 = Math.min(window.devicePixelRatio || 1, 2);
|
const appRenderPixelRatio = 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 = Math.min(window.devicePixelRatio || 1, 2);
|
const reflectionPixelRatio = 2;
|
||||||
const pageTextureWidth = 3072;
|
const pageTextureWidth = 3072;
|
||||||
const reflectionTargetSize = new THREE.Vector2();
|
const reflectionTargetSize = new THREE.Vector2();
|
||||||
const pageRaycaster = new THREE.Raycaster();
|
const pageRaycaster = new THREE.Raycaster();
|
||||||
@@ -56,6 +56,17 @@ let renderedFrameCount = 0;
|
|||||||
let staticSceneBuffersDirty = true;
|
let staticSceneBuffersDirty = true;
|
||||||
let lastStaticCameraSignature = '';
|
let lastStaticCameraSignature = '';
|
||||||
|
|
||||||
|
function reportLabProgress(percent, message) {
|
||||||
|
if (typeof appInitialState.reportProgress === 'function') {
|
||||||
|
appInitialState.reportProgress(percent, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reportLabStep(percent, message) {
|
||||||
|
reportLabProgress(percent, message);
|
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
}
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x080604);
|
scene.background = new THREE.Color(0x080604);
|
||||||
scene.fog = new THREE.FogExp2(0x080604, 0.035);
|
scene.fog = new THREE.FogExp2(0x080604, 0.035);
|
||||||
@@ -174,6 +185,7 @@ let pendingPageFlips = 0;
|
|||||||
const paperColor = new THREE.Color(0xf1ead2);
|
const paperColor = new THREE.Color(0xf1ead2);
|
||||||
const inkColor = '#1a1009';
|
const inkColor = '#1a1009';
|
||||||
|
|
||||||
|
await reportLabStep(48, 'Preparing high-resolution page textures');
|
||||||
const leftCanvas = createPageCanvas('left');
|
const leftCanvas = createPageCanvas('left');
|
||||||
const rightCanvas = createPageCanvas('right');
|
const rightCanvas = createPageCanvas('right');
|
||||||
const leftTexture = new THREE.CanvasTexture(leftCanvas);
|
const leftTexture = new THREE.CanvasTexture(leftCanvas);
|
||||||
@@ -185,10 +197,15 @@ const rightTexture = new THREE.CanvasTexture(rightCanvas);
|
|||||||
texture.magFilter = THREE.LinearFilter;
|
texture.magFilter = THREE.LinearFilter;
|
||||||
texture.generateMipmaps = true;
|
texture.generateMipmaps = true;
|
||||||
});
|
});
|
||||||
|
await reportLabStep(52, 'Generating leather texture set');
|
||||||
const leatherTextures = createLeatherTextures();
|
const leatherTextures = createLeatherTextures();
|
||||||
|
await reportLabStep(56, 'Generating spine cloth texture set');
|
||||||
const spineClothTextures = createSpineClothTextures();
|
const spineClothTextures = createSpineClothTextures();
|
||||||
|
await reportLabStep(60, 'Generating headband texture set');
|
||||||
const headbandTextures = createHeadbandTextures();
|
const headbandTextures = createHeadbandTextures();
|
||||||
|
await reportLabStep(64, 'Generating paper texture set');
|
||||||
const paperTextures = createHardcoverPaperTextures();
|
const paperTextures = createHardcoverPaperTextures();
|
||||||
|
await reportLabStep(68, 'Creating WebGL book materials');
|
||||||
|
|
||||||
const materials = {
|
const materials = {
|
||||||
leather: new THREE.MeshStandardMaterial({
|
leather: new THREE.MeshStandardMaterial({
|
||||||
@@ -344,11 +361,18 @@ configureBookShadowReceiver(materials.rightPage, 0.18);
|
|||||||
configureBookShadowReceiver(materials.spineCloth, 0.48);
|
configureBookShadowReceiver(materials.spineCloth, 0.48);
|
||||||
configureBookShadowReceiver(materials.headband, 0.62);
|
configureBookShadowReceiver(materials.headband, 0.62);
|
||||||
|
|
||||||
|
await reportLabStep(70, 'Building reflective table');
|
||||||
buildTable();
|
buildTable();
|
||||||
|
await reportLabStep(74, 'Building candle lighting');
|
||||||
buildLighting();
|
buildLighting();
|
||||||
|
await reportLabStep(78, 'Building physical book stack');
|
||||||
buildBook();
|
buildBook();
|
||||||
notifyBookPageCountChanged();
|
notifyBookPageCountChanged();
|
||||||
|
await reportLabStep(82, 'Loading room reflection texture');
|
||||||
loadAiRoomReflection();
|
loadAiRoomReflection();
|
||||||
|
await reportLabStep(86, 'Preparing static shadow and mirror maps');
|
||||||
|
primeSceneForLoader();
|
||||||
|
await reportLabStep(90, 'Compiled WebGL scene passes');
|
||||||
window.BookLabDebug = {
|
window.BookLabDebug = {
|
||||||
textures: generatedTextureCanvases,
|
textures: generatedTextureCanvases,
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -1534,6 +1558,7 @@ function handlePageCanvases(event) {
|
|||||||
drawCanvasPageTexture(rightCanvas, detail.right, 'right');
|
drawCanvasPageTexture(rightCanvas, detail.right, 'right');
|
||||||
rightTexture.needsUpdate = true;
|
rightTexture.needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
document.documentElement.dataset.webglPageTextureMetrics = JSON.stringify({
|
document.documentElement.dataset.webglPageTextureMetrics = JSON.stringify({
|
||||||
width: leftCanvas.width,
|
width: leftCanvas.width,
|
||||||
height: leftCanvas.height,
|
height: leftCanvas.height,
|
||||||
@@ -2586,6 +2611,7 @@ function loadAiRoomReflection() {
|
|||||||
if (tableShader) {
|
if (tableShader) {
|
||||||
tableShader.uniforms.roomReflectionMap.value = texture;
|
tableShader.uniforms.roomReflectionMap.value = texture;
|
||||||
}
|
}
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
|
|
||||||
const image = texture.image;
|
const image = texture.image;
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
@@ -2596,11 +2622,22 @@ function loadAiRoomReflection() {
|
|||||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||||
generatedTextureCanvases.aiRoomReflection = canvas;
|
generatedTextureCanvases.aiRoomReflection = canvas;
|
||||||
tintAmbientFromCanvas(canvas);
|
tintAmbientFromCanvas(canvas);
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
}, undefined, () => {
|
}, undefined, () => {
|
||||||
tintAmbientFromCanvas(generatedTextureCanvases.roomReflection);
|
tintAmbientFromCanvas(generatedTextureCanvases.roomReflection);
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function primeSceneForLoader() {
|
||||||
|
updateCameraRig(0);
|
||||||
|
updateCandleShadowUniforms();
|
||||||
|
updateBookShadowMaps();
|
||||||
|
updateTableReflection();
|
||||||
|
renderer.compile(scene, camera);
|
||||||
|
staticSceneBuffersDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
function tintAmbientFromCanvas(canvas) {
|
function tintAmbientFromCanvas(canvas) {
|
||||||
if (!canvas || !candleBounceLight) return;
|
if (!canvas || !candleBounceLight) return;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const DEFAULT_BOOK_PROGRESS = 0.5;
|
|||||||
class WebGLBookSceneModule extends BaseModule {
|
class WebGLBookSceneModule extends BaseModule {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('webgl-book-scene', 'WebGL Book Scene');
|
super('webgl-book-scene', 'WebGL Book Scene');
|
||||||
this.dependencies = ['persistence-manager', 'localization'];
|
this.dependencies = ['persistence-manager', 'localization', 'book-texture-renderer'];
|
||||||
this.persistenceManager = null;
|
this.persistenceManager = null;
|
||||||
this.localization = null;
|
this.localization = null;
|
||||||
this.mode = '2d';
|
this.mode = '2d';
|
||||||
@@ -73,6 +73,8 @@ class WebGLBookSceneModule extends BaseModule {
|
|||||||
this.reportProgress(35, 'Creating WebGL host');
|
this.reportProgress(35, 'Creating WebGL host');
|
||||||
this.ensureShell();
|
this.ensureShell();
|
||||||
this.installPreferenceBridge();
|
this.installPreferenceBridge();
|
||||||
|
this.reportProgress(45, 'Loading WebGL scene modules');
|
||||||
|
await this.initializeScene();
|
||||||
|
|
||||||
this.reportProgress(100, 'WebGL book host ready');
|
this.reportProgress(100, 'WebGL book host ready');
|
||||||
return true;
|
return true;
|
||||||
@@ -173,7 +175,10 @@ class WebGLBookSceneModule extends BaseModule {
|
|||||||
window.WebGLBookInitialState = {
|
window.WebGLBookInitialState = {
|
||||||
appMode: true,
|
appMode: true,
|
||||||
pageCount,
|
pageCount,
|
||||||
progress
|
progress,
|
||||||
|
reportProgress: (percent, message) => {
|
||||||
|
this.reportProgress(percent, message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +298,10 @@ class WebGLBookSceneModule extends BaseModule {
|
|||||||
const cacheBuster = window.MODULE_CACHE_BUSTER || Date.now();
|
const cacheBuster = window.MODULE_CACHE_BUSTER || Date.now();
|
||||||
this.labImportPromise = import(`/js/webgl-book-lab.js?v=${encodeURIComponent(cacheBuster)}`);
|
this.labImportPromise = import(`/js/webgl-book-lab.js?v=${encodeURIComponent(cacheBuster)}`);
|
||||||
await this.labImportPromise;
|
await this.labImportPromise;
|
||||||
|
this.reportProgress(94, 'Uploading initial book page textures');
|
||||||
|
window.BookTextureRenderer?.publishSpread?.();
|
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
this.reportProgress(96, 'Binding WebGL page controls');
|
||||||
this.installTextureEventBridge();
|
this.installTextureEventBridge();
|
||||||
this.triggerTextureRefresh();
|
this.triggerTextureRefresh();
|
||||||
return this.labImportPromise;
|
return this.labImportPromise;
|
||||||
@@ -459,11 +468,7 @@ class WebGLBookSceneModule extends BaseModule {
|
|||||||
if (this.mode === '3d') {
|
if (this.mode === '3d') {
|
||||||
this.createLabHost();
|
this.createLabHost();
|
||||||
this.installPreferenceBridge();
|
this.installPreferenceBridge();
|
||||||
this.initializeScene()
|
if (this.labImportPromise) this.triggerTextureRefresh();
|
||||||
.then(() => this.triggerTextureRefresh())
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('WebGLBookScene: Failed to initialize procedural scene', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const title = document.getElementById('game_title')?.textContent?.trim();
|
const title = document.getElementById('game_title')?.textContent?.trim();
|
||||||
const label = document.getElementById('lab_title');
|
const label = document.getElementById('lab_title');
|
||||||
|
|||||||
Reference in New Issue
Block a user