Start texture-space book renderer
This commit is contained in:
+26
-96
@@ -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=20260606-webgl-no-menu-offscreen-dom';
|
||||
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260606-book-page-format-restore';
|
||||
|
||||
const canvas = document.getElementById('scene');
|
||||
canvas.style.cursor = 'grab';
|
||||
@@ -25,9 +25,6 @@ const appInitialState = window.WebGLBookInitialState || {};
|
||||
const tableDebugName = urlParams.get('tableDebug') || 'none';
|
||||
const tableDebugMode = tableDebugModes[tableDebugName] ?? tableDebugModes.none;
|
||||
const isAppIntegrationMode = appInitialState.appMode === true;
|
||||
const html2CanvasPromise = isAppIntegrationMode
|
||||
? import('https://esm.sh/html2canvas@1.4.1')
|
||||
: null;
|
||||
const labStatus = document.getElementById('lab_status');
|
||||
if (labStatus && tableDebugMode !== tableDebugModes.none) {
|
||||
labStatus.textContent = tableDebugName === 'ao' ? 'scene debug: SSAO' : `table debug: ${tableDebugName}`;
|
||||
@@ -44,13 +41,9 @@ const generatedTextureCanvases = {};
|
||||
const maxTextureAnisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
const reflectionPixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
const pageTextureWidth = isAppIntegrationMode ? 1280 : 3200;
|
||||
const appPageTextureInset = 0;
|
||||
const reflectionTargetSize = new THREE.Vector2();
|
||||
const pageRaycaster = new THREE.Raycaster();
|
||||
const pointerNdc = new THREE.Vector2();
|
||||
let pageTextureRenderSerial = 0;
|
||||
let pageTextureRenderInProgress = false;
|
||||
let pageTextureRenderPending = false;
|
||||
let sceneComposerTarget = null;
|
||||
let composer = null;
|
||||
let sceneRenderPass = null;
|
||||
@@ -402,14 +395,13 @@ window.BookLabDebug = {
|
||||
return bookPageCount;
|
||||
},
|
||||
redrawPageTextures() {
|
||||
redrawPageTexturesFromDom();
|
||||
window.BookTextureRenderer?.publishSpread?.();
|
||||
return true;
|
||||
},
|
||||
getTextureInfo() {
|
||||
return {
|
||||
pageTextureWidth,
|
||||
pageTextureHeight: leftCanvas.height,
|
||||
appPageTextureInset,
|
||||
debug: getPageTextureDebugState()
|
||||
};
|
||||
},
|
||||
@@ -424,10 +416,11 @@ window.BookLabDebug = {
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
document.addEventListener('webgl-book:redraw-pages', redrawPageTexturesFromDom);
|
||||
document.addEventListener('webgl-book:page-canvases', handlePageCanvases);
|
||||
installBookControls();
|
||||
installCameraControls();
|
||||
resize();
|
||||
document.dispatchEvent(new CustomEvent('webgl-book:scene-ready'));
|
||||
animate();
|
||||
|
||||
function buildTable() {
|
||||
@@ -1444,35 +1437,24 @@ function syncBookControls() {
|
||||
if (fastForwardButton) fastForwardButton.disabled = busy || !canPageFlip(1);
|
||||
}
|
||||
|
||||
function redrawPageTexturesFromDom() {
|
||||
if (pageTextureRenderInProgress) {
|
||||
pageTextureRenderPending = true;
|
||||
return;
|
||||
function handlePageCanvases(event) {
|
||||
const detail = event.detail || {};
|
||||
if (detail.left) {
|
||||
drawCanvasPageTexture(leftCanvas, detail.left, 'left');
|
||||
leftTexture.needsUpdate = true;
|
||||
}
|
||||
const leftSource = document.getElementById('page_left');
|
||||
const rightSource = document.getElementById('page_right');
|
||||
if (!leftSource && !rightSource) return;
|
||||
pageTextureRenderInProgress = true;
|
||||
const serial = ++pageTextureRenderSerial;
|
||||
(async () => {
|
||||
try {
|
||||
if (leftSource && await drawDomPageTexture(leftCanvas, leftSource, 'left')) {
|
||||
leftTexture.needsUpdate = true;
|
||||
}
|
||||
if (rightSource && await drawDomPageTexture(rightCanvas, rightSource, 'right')) {
|
||||
rightTexture.needsUpdate = true;
|
||||
}
|
||||
} finally {
|
||||
pageTextureRenderInProgress = false;
|
||||
if (pageTextureRenderPending && serial === pageTextureRenderSerial) {
|
||||
pageTextureRenderPending = false;
|
||||
redrawPageTexturesFromDom();
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (detail.right) {
|
||||
drawCanvasPageTexture(rightCanvas, detail.right, 'right');
|
||||
rightTexture.needsUpdate = true;
|
||||
}
|
||||
document.documentElement.dataset.webglPageTextureMetrics = JSON.stringify({
|
||||
width: leftCanvas.width,
|
||||
height: leftCanvas.height,
|
||||
source: 'book-texture-renderer'
|
||||
});
|
||||
}
|
||||
|
||||
async function drawDomPageTexture(canvas, source, side) {
|
||||
function drawCanvasPageTexture(canvas, sourceCanvas, side) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#fffaf0';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
@@ -1484,9 +1466,9 @@ async function drawDomPageTexture(canvas, source, side) {
|
||||
ctx.fillStyle = shade;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const painted = await paintRasterizedDomPage(ctx, canvas, source);
|
||||
updatePageTextureDebugState(side, canvas, source, painted);
|
||||
return painted;
|
||||
ctx.drawImage(sourceCanvas, 0, 0, canvas.width, canvas.height);
|
||||
updatePageTextureDebugState(side, canvas, sourceCanvas, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
function getPageTextureDebugState() {
|
||||
@@ -1505,8 +1487,8 @@ function updatePageTextureDebugState(side, canvas, source, painted) {
|
||||
painted,
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
sourceId: source.id || '',
|
||||
sourceTextLength: source.textContent?.trim().length || 0,
|
||||
sourceId: source?.id || 'book-texture-renderer',
|
||||
sourceTextLength: 0,
|
||||
darkPixels: countPageTextureDarkPixels(canvas)
|
||||
};
|
||||
document.documentElement.dataset.webglPageTextures = JSON.stringify(state);
|
||||
@@ -1530,56 +1512,6 @@ function countPageTextureDarkPixels(canvas) {
|
||||
return darkPixels;
|
||||
}
|
||||
|
||||
async function paintRasterizedDomPage(ctx, canvas, source) {
|
||||
const pageRect = source.getBoundingClientRect();
|
||||
if (pageRect.width <= 0 || pageRect.height <= 0) return false;
|
||||
const captured = await captureDomPageWithHtml2Canvas(source, pageRect, canvas);
|
||||
if (captured) {
|
||||
drawCapturedPageCanvas(ctx, canvas, captured);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function captureDomPageWithHtml2Canvas(source, pageRect, targetCanvas) {
|
||||
if (!html2CanvasPromise) return null;
|
||||
try {
|
||||
const module = await html2CanvasPromise;
|
||||
const html2canvas = module.default || module;
|
||||
return await html2canvas(source, {
|
||||
backgroundColor: null,
|
||||
logging: false,
|
||||
useCORS: true,
|
||||
allowTaint: false,
|
||||
foreignObjectRendering: true,
|
||||
x: pageRect.left,
|
||||
y: pageRect.top,
|
||||
width: pageRect.width,
|
||||
height: pageRect.height,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
windowWidth: Math.ceil(Math.max(window.innerWidth, pageRect.right)),
|
||||
windowHeight: Math.ceil(Math.max(window.innerHeight, pageRect.bottom)),
|
||||
scale: Math.max(1, targetCanvas.width / pageRect.width)
|
||||
});
|
||||
} catch (error) {
|
||||
document.documentElement.dataset.webglLastCaptureError = error?.message || String(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function drawCapturedPageCanvas(ctx, canvas, captured) {
|
||||
const insetX = canvas.width * appPageTextureInset;
|
||||
const insetY = canvas.height * appPageTextureInset * 0.35;
|
||||
ctx.drawImage(
|
||||
captured,
|
||||
insetX,
|
||||
insetY,
|
||||
canvas.width - insetX * 2,
|
||||
canvas.height - insetY * 2
|
||||
);
|
||||
}
|
||||
|
||||
function projectPointerToPage(clientX, clientY) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (rect.width <= 0 || rect.height <= 0) return null;
|
||||
@@ -1592,10 +1524,8 @@ function projectPointerToPage(clientX, clientY) {
|
||||
for (const hit of intersections) {
|
||||
const pageSide = textureHitPageSide(hit);
|
||||
if (!pageSide || !hit.uv) continue;
|
||||
const insetX = appPageTextureInset;
|
||||
const insetY = appPageTextureInset * 0.35;
|
||||
const mappedX = THREE.MathUtils.clamp((hit.uv.x - insetX) / Math.max(0.001, 1 - insetX * 2), 0, 1);
|
||||
const mappedY = 1 - THREE.MathUtils.clamp((hit.uv.y - insetY) / Math.max(0.001, 1 - insetY * 2), 0, 1);
|
||||
const mappedX = THREE.MathUtils.clamp(hit.uv.x, 0, 1);
|
||||
const mappedY = 1 - THREE.MathUtils.clamp(hit.uv.y, 0, 1);
|
||||
return {
|
||||
pageId: pageSide === 'left' ? 'page_left' : 'page_right',
|
||||
x: mappedX,
|
||||
|
||||
Reference in New Issue
Block a user