Restore WebGL book quality settings
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=20260516-scroll-window"></script>
|
<script type="module" src="/js/loader.js?v=20260607-webgl-physical-stack-quality"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* 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';
|
||||||
|
|
||||||
class BookPageFormatModule extends BaseModule {
|
class BookPageFormatModule extends BaseModule {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -17,8 +18,13 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
margins: Object.freeze({
|
margins: Object.freeze({
|
||||||
topIn: 0.46,
|
topIn: 0.46,
|
||||||
bottomIn: 0.58,
|
bottomIn: 0.58,
|
||||||
innerIn: 0.56,
|
innerBaseIn: 0.375,
|
||||||
outerIn: 0.44
|
innerMinIn: 0.44,
|
||||||
|
innerMaxIn: 0.68,
|
||||||
|
innerThicknessFactor: 0.25,
|
||||||
|
outerBaseIn: 0.44,
|
||||||
|
outerThicknessFactor: 0.04,
|
||||||
|
outerMaxIn: 0.5
|
||||||
}),
|
}),
|
||||||
typography: Object.freeze({
|
typography: Object.freeze({
|
||||||
fontFamily: '"EB Garamond", "EB Garamond 12", serif',
|
fontFamily: '"EB Garamond", "EB Garamond 12", serif',
|
||||||
@@ -28,16 +34,27 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
dropCapLines: 2
|
dropCapLines: 2
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
this.pageCount = snapProceduralPageCount(window.WebGLBookInitialState?.pageCount ?? 300);
|
||||||
|
|
||||||
this.bindMethods([
|
this.bindMethods([
|
||||||
'getFormat',
|
'getFormat',
|
||||||
'getAspectRatio',
|
'getAspectRatio',
|
||||||
'getTextureMetrics',
|
'getTextureMetrics',
|
||||||
|
'setPageCount',
|
||||||
|
'getPageCount',
|
||||||
|
'getDynamicMargins',
|
||||||
'inchesToTexture'
|
'inchesToTexture'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
this.addEventListener(document, 'webgl-book:page-count-changed', (event) => {
|
||||||
|
this.setPageCount(event.detail?.pageCount);
|
||||||
|
});
|
||||||
|
this.addEventListener(document, 'preference-updated', (event) => {
|
||||||
|
const detail = event.detail || {};
|
||||||
|
if (detail.category === 'webgl' && detail.key === 'bookPageCount') this.setPageCount(detail.value);
|
||||||
|
});
|
||||||
this.reportProgress(100, 'Book page format ready');
|
this.reportProgress(100, 'Book page format ready');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -54,14 +71,49 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
return (Number(valueIn) / this.format.trim.heightIn) * textureHeight;
|
return (Number(valueIn) / this.format.trim.heightIn) * textureHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTextureMetrics(textureWidth = 1280) {
|
setPageCount(value) {
|
||||||
|
const nextPageCount = snapProceduralPageCount(value ?? this.pageCount);
|
||||||
|
if (nextPageCount === this.pageCount) return this.pageCount;
|
||||||
|
this.pageCount = nextPageCount;
|
||||||
|
return this.pageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPageCount() {
|
||||||
|
return this.pageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDynamicMargins(pageCount = this.pageCount) {
|
||||||
|
const marginConfig = this.format.margins;
|
||||||
|
const thickness = calculateProceduralBookThickness(pageCount);
|
||||||
|
const innerIn = Math.min(
|
||||||
|
marginConfig.innerMaxIn,
|
||||||
|
Math.max(
|
||||||
|
marginConfig.innerMinIn,
|
||||||
|
marginConfig.innerBaseIn + thickness.textBlockThicknessIn * marginConfig.innerThicknessFactor
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const outerIn = Math.min(
|
||||||
|
marginConfig.outerMaxIn,
|
||||||
|
marginConfig.outerBaseIn + thickness.textBlockThicknessIn * marginConfig.outerThicknessFactor
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
topIn: 0.46,
|
||||||
|
bottomIn: 0.58,
|
||||||
|
innerIn,
|
||||||
|
outerIn,
|
||||||
|
thickness
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTextureMetrics(textureWidth = 1280, 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 margins = {
|
const margins = {
|
||||||
top: this.inchesToTexture(this.format.margins.topIn, height),
|
top: this.inchesToTexture(dynamicMargins.topIn, height),
|
||||||
bottom: this.inchesToTexture(this.format.margins.bottomIn, height),
|
bottom: this.inchesToTexture(dynamicMargins.bottomIn, height),
|
||||||
inner: this.inchesToTexture(this.format.margins.innerIn, height),
|
inner: this.inchesToTexture(dynamicMargins.innerIn, height),
|
||||||
outer: this.inchesToTexture(this.format.margins.outerIn, height)
|
outer: this.inchesToTexture(dynamicMargins.outerIn, height)
|
||||||
};
|
};
|
||||||
const content = {
|
const content = {
|
||||||
x: margins.outer,
|
x: margins.outer,
|
||||||
@@ -89,6 +141,13 @@ class BookPageFormatModule extends BaseModule {
|
|||||||
margins,
|
margins,
|
||||||
content,
|
content,
|
||||||
contentBySide,
|
contentBySide,
|
||||||
|
marginsIn: {
|
||||||
|
top: dynamicMargins.topIn,
|
||||||
|
bottom: dynamicMargins.bottomIn,
|
||||||
|
inner: dynamicMargins.innerIn,
|
||||||
|
outer: dynamicMargins.outerIn
|
||||||
|
},
|
||||||
|
thickness: dynamicMargins.thickness,
|
||||||
linesPerPage,
|
linesPerPage,
|
||||||
bodyFontSizePx,
|
bodyFontSizePx,
|
||||||
typographyLineHeightPx,
|
typographyLineHeightPx,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
'publishSpread',
|
'publishSpread',
|
||||||
'getPageCanvas',
|
'getPageCanvas',
|
||||||
'getHitMap',
|
'getHitMap',
|
||||||
|
'handlePageCountChanged',
|
||||||
'handleSceneReady'
|
'handleSceneReady'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,7 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
this.reportProgress(20, 'Preparing page texture canvases');
|
this.reportProgress(20, 'Preparing page texture canvases');
|
||||||
this.createPageCanvases();
|
this.createPageCanvases();
|
||||||
this.drawEmptySpread();
|
this.drawEmptySpread();
|
||||||
|
this.addEventListener(document, 'webgl-book:page-count-changed', this.handlePageCountChanged);
|
||||||
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 latestBlockId = event.detail?.latestBlockId;
|
||||||
@@ -84,7 +86,7 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
createPageCanvases(textureWidth = 1280) {
|
createPageCanvases(textureWidth = 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');
|
||||||
@@ -408,6 +410,12 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
return this.hitMaps[side] || [];
|
return this.hitMaps[side] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePageCountChanged(event) {
|
||||||
|
this.pageFormat?.setPageCount?.(event.detail?.pageCount);
|
||||||
|
this.createPageCanvases();
|
||||||
|
this.drawSpread(this.currentSpread || this.pagination?.getCurrentSpread?.());
|
||||||
|
}
|
||||||
|
|
||||||
handleSceneReady() {
|
handleSceneReady() {
|
||||||
this.publishSpread();
|
this.publishSpread();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ const ModuleState = {
|
|||||||
ERROR: 'ERROR'
|
ERROR: 'ERROR'
|
||||||
};
|
};
|
||||||
|
|
||||||
const MODULE_CACHE_BUSTER = '20260607-webgl-page-uv-endpoints';
|
const MODULE_CACHE_BUSTER = '20260607-webgl-physical-stack-quality';
|
||||||
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ export const PROCEDURAL_BOOK = {
|
|||||||
PAGE_COUNT_MIN: 40,
|
PAGE_COUNT_MIN: 40,
|
||||||
PAGE_COUNT_MAX: 500,
|
PAGE_COUNT_MAX: 500,
|
||||||
PAGE_COUNT_STEP: 10,
|
PAGE_COUNT_STEP: 10,
|
||||||
|
TRIM_WIDTH_IN: 4.25,
|
||||||
|
TRIM_HEIGHT_IN: 6.87,
|
||||||
|
PAPER_CALIPER_MM: 0.097,
|
||||||
|
PAGES_PER_BUNDLE: 10,
|
||||||
PAGE_LINE_SEGMENTS: 48,
|
PAGE_LINE_SEGMENTS: 48,
|
||||||
PAGE_DEPTH: 2.24,
|
PAGE_DEPTH: 2.24,
|
||||||
PAGE_WIDTH: 2.24 * (4.25 / 6.87),
|
PAGE_WIDTH: 2.24 * (4.25 / 6.87),
|
||||||
@@ -16,11 +20,16 @@ export const PROCEDURAL_BOOK = {
|
|||||||
raisedHingeY: 0.056,
|
raisedHingeY: 0.056,
|
||||||
paperContactOffset: 0.0012,
|
paperContactOffset: 0.0012,
|
||||||
singlePageCoverGap: 0.006,
|
singlePageCoverGap: 0.006,
|
||||||
bundleSpacing: 0.014
|
bundleSpacing: 0.0062
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
PROCEDURAL_BOOK.PAGE_SPLINE_LENGTH = PROCEDURAL_BOOK.PAGE_WIDTH;
|
PROCEDURAL_BOOK.PAGE_SPLINE_LENGTH = PROCEDURAL_BOOK.PAGE_WIDTH;
|
||||||
PROCEDURAL_BOOK.COVER_OVERHANG = (PROCEDURAL_BOOK.COVER_DEPTH - PROCEDURAL_BOOK.PAGE_DEPTH) * 0.5;
|
PROCEDURAL_BOOK.COVER_OVERHANG = (PROCEDURAL_BOOK.COVER_DEPTH - PROCEDURAL_BOOK.PAGE_DEPTH) * 0.5;
|
||||||
|
PROCEDURAL_BOOK.MODEL_UNITS_PER_INCH = PROCEDURAL_BOOK.PAGE_DEPTH / PROCEDURAL_BOOK.TRIM_HEIGHT_IN;
|
||||||
|
PROCEDURAL_BOOK.PAPER_CALIPER_IN = PROCEDURAL_BOOK.PAPER_CALIPER_MM / 25.4;
|
||||||
|
PROCEDURAL_BOOK.SHEET_THICKNESS_MODEL = PROCEDURAL_BOOK.PAPER_CALIPER_IN * PROCEDURAL_BOOK.MODEL_UNITS_PER_INCH;
|
||||||
|
PROCEDURAL_BOOK.BUNDLE_SHEET_COUNT = PROCEDURAL_BOOK.PAGES_PER_BUNDLE / 2;
|
||||||
|
PROCEDURAL_BOOK.BUNDLE_THICKNESS_MODEL = PROCEDURAL_BOOK.SHEET_THICKNESS_MODEL * PROCEDURAL_BOOK.BUNDLE_SHEET_COUNT;
|
||||||
|
|
||||||
export function snapProceduralPageCount(value) {
|
export function snapProceduralPageCount(value) {
|
||||||
const parsed = Number.parseFloat(value);
|
const parsed = Number.parseFloat(value);
|
||||||
@@ -32,6 +41,33 @@ export function snapProceduralPageCount(value) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function calculateProceduralBookThickness(pageCountValue) {
|
||||||
|
const pageCount = snapProceduralPageCount(pageCountValue);
|
||||||
|
const sheetCount = Math.max(1, pageCount / 2);
|
||||||
|
const bundleCount = Math.max(4, Math.round(pageCount / PROCEDURAL_BOOK.PAGES_PER_BUNDLE));
|
||||||
|
const sheetThicknessIn = PROCEDURAL_BOOK.PAPER_CALIPER_IN;
|
||||||
|
const sheetThicknessModel = PROCEDURAL_BOOK.SHEET_THICKNESS_MODEL;
|
||||||
|
const bundleSheetCount = PROCEDURAL_BOOK.BUNDLE_SHEET_COUNT;
|
||||||
|
const bundleThicknessIn = sheetThicknessIn * bundleSheetCount;
|
||||||
|
const bundleThicknessModel = sheetThicknessModel * bundleSheetCount;
|
||||||
|
const textBlockThicknessIn = sheetThicknessIn * sheetCount;
|
||||||
|
const textBlockThicknessModel = sheetThicknessModel * sheetCount;
|
||||||
|
return {
|
||||||
|
pageCount,
|
||||||
|
sheetCount,
|
||||||
|
bundleCount,
|
||||||
|
sheetThicknessIn,
|
||||||
|
sheetThicknessModel,
|
||||||
|
bundleSheetCount,
|
||||||
|
bundleThicknessIn,
|
||||||
|
bundleThicknessModel,
|
||||||
|
tenPageStackThicknessIn: bundleThicknessIn,
|
||||||
|
tenPageStackThicknessModel: bundleThicknessModel,
|
||||||
|
textBlockThicknessIn,
|
||||||
|
textBlockThicknessModel
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createProceduralBookModel(options = {}) {
|
export function createProceduralBookModel(options = {}) {
|
||||||
const context = createBookContext(options);
|
const context = createBookContext(options);
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
@@ -104,7 +140,8 @@ function calculateBookModel(context) {
|
|||||||
const pageWidth = PROCEDURAL_BOOK.PAGE_WIDTH;
|
const pageWidth = PROCEDURAL_BOOK.PAGE_WIDTH;
|
||||||
const pageDepth = PROCEDURAL_BOOK.PAGE_DEPTH;
|
const pageDepth = PROCEDURAL_BOOK.PAGE_DEPTH;
|
||||||
const coverDepth = PROCEDURAL_BOOK.COVER_DEPTH;
|
const coverDepth = PROCEDURAL_BOOK.COVER_DEPTH;
|
||||||
const bundleCount = Math.max(4, Math.round(context.pageCount / 10));
|
const thickness = calculateProceduralBookThickness(context.pageCount);
|
||||||
|
const bundleCount = thickness.bundleCount;
|
||||||
const spineWidth = calculateSpineWidth(bundleCount);
|
const spineWidth = calculateSpineWidth(bundleCount);
|
||||||
const leftCount = calculateLeftBundleCount(context, bundleCount);
|
const leftCount = calculateLeftBundleCount(context, bundleCount);
|
||||||
const spineHalf = spineArcHalf(spineWidth);
|
const spineHalf = spineArcHalf(spineWidth);
|
||||||
@@ -125,6 +162,7 @@ function calculateBookModel(context) {
|
|||||||
coverOuterX,
|
coverOuterX,
|
||||||
bundleSpacing,
|
bundleSpacing,
|
||||||
leftCount,
|
leftCount,
|
||||||
|
thickness,
|
||||||
lines
|
lines
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -872,9 +910,9 @@ function pointAtMeasuredPathDistance(support, distance) {
|
|||||||
function calculateSpineWidth(bundleCount) {
|
function calculateSpineWidth(bundleCount) {
|
||||||
const minimumWidth = 0.006;
|
const minimumWidth = 0.006;
|
||||||
if (bundleCount <= 1) return minimumWidth;
|
if (bundleCount <= 1) return minimumWidth;
|
||||||
const targetArcLength = (bundleCount - 1) * PROCEDURAL_BOOK.PROFILE.bundleSpacing + PROCEDURAL_BOOK.OPEN_SEAM_GAP;
|
const targetArcLength = (bundleCount - 1) * PROCEDURAL_BOOK.BUNDLE_THICKNESS_MODEL + PROCEDURAL_BOOK.OPEN_SEAM_GAP;
|
||||||
let low = minimumWidth;
|
let low = minimumWidth;
|
||||||
let high = Math.max(minimumWidth, bundleCount * PROCEDURAL_BOOK.PROFILE.bundleSpacing * 1.4);
|
let high = Math.max(minimumWidth, bundleCount * PROCEDURAL_BOOK.BUNDLE_THICKNESS_MODEL * 1.4);
|
||||||
while (measureSpineArcLength(high) < targetArcLength) high *= 1.25;
|
while (measureSpineArcLength(high) < targetArcLength) high *= 1.25;
|
||||||
for (let i = 0; i < 24; i += 1) {
|
for (let i = 0; i < 24; i += 1) {
|
||||||
const mid = (low + high) * 0.5;
|
const mid = (low + high) * 0.5;
|
||||||
@@ -887,7 +925,7 @@ function calculateSpineWidth(bundleCount) {
|
|||||||
function calculateBundleSpacing(bundleCount, spineWidth, leftCount) {
|
function calculateBundleSpacing(bundleCount, spineWidth, leftCount) {
|
||||||
const rightCount = bundleCount - leftCount;
|
const rightCount = bundleCount - leftCount;
|
||||||
const stackIntervals = Math.max(0, leftCount - 1) + Math.max(0, rightCount - 1);
|
const stackIntervals = Math.max(0, leftCount - 1) + Math.max(0, rightCount - 1);
|
||||||
if (stackIntervals <= 0) return PROCEDURAL_BOOK.PROFILE.bundleSpacing;
|
if (stackIntervals <= 0) return PROCEDURAL_BOOK.BUNDLE_THICKNESS_MODEL;
|
||||||
return Math.max(0.001, (measureSpineArcLength(spineWidth) - PROCEDURAL_BOOK.OPEN_SEAM_GAP) / stackIntervals);
|
return Math.max(0.001, (measureSpineArcLength(spineWidth) - PROCEDURAL_BOOK.OPEN_SEAM_GAP) / stackIntervals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+127
-54
@@ -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-page-uv-endpoints';
|
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-physical-stack-quality';
|
||||||
|
|
||||||
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 ? 1 : Math.min(window.devicePixelRatio || 1, 2);
|
const appRenderPixelRatio = 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,8 +40,8 @@ renderer.shadowMap.type = THREE.VSMShadowMap;
|
|||||||
|
|
||||||
const generatedTextureCanvases = {};
|
const generatedTextureCanvases = {};
|
||||||
const maxTextureAnisotropy = renderer.capabilities.getMaxAnisotropy();
|
const maxTextureAnisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||||
const reflectionPixelRatio = isAppIntegrationMode ? 0.5 : Math.min(window.devicePixelRatio || 1, 2);
|
const reflectionPixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
const pageTextureWidth = isAppIntegrationMode ? 1280 : 3200;
|
const pageTextureWidth = 3072;
|
||||||
const reflectionTargetSize = new THREE.Vector2();
|
const reflectionTargetSize = new THREE.Vector2();
|
||||||
const pageRaycaster = new THREE.Raycaster();
|
const pageRaycaster = new THREE.Raycaster();
|
||||||
const pointerNdc = new THREE.Vector2();
|
const pointerNdc = new THREE.Vector2();
|
||||||
@@ -53,6 +53,8 @@ let sceneSmaaPass = null;
|
|||||||
let sceneOutputPass = null;
|
let sceneOutputPass = null;
|
||||||
const aoExcludedObjects = new Set();
|
const aoExcludedObjects = new Set();
|
||||||
let renderedFrameCount = 0;
|
let renderedFrameCount = 0;
|
||||||
|
let staticSceneBuffersDirty = true;
|
||||||
|
let lastStaticCameraSignature = '';
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x080604);
|
scene.background = new THREE.Color(0x080604);
|
||||||
@@ -65,13 +67,13 @@ 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 ? 640 : 4096;
|
const tableReflectionBaseWidth = 4096;
|
||||||
const tableReflectionBaseHeight = isAppIntegrationMode ? 360 : 2304;
|
const tableReflectionBaseHeight = 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,
|
||||||
stencilBuffer: false,
|
stencilBuffer: false,
|
||||||
samples: renderer.capabilities.isWebGL2 ? (isAppIntegrationMode ? 0 : 8) : 0
|
samples: renderer.capabilities.isWebGL2 ? 8 : 0
|
||||||
});
|
});
|
||||||
tableReflectionTarget.texture.colorSpace = THREE.SRGBColorSpace;
|
tableReflectionTarget.texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
tableReflectionTarget.texture.minFilter = THREE.LinearFilter;
|
tableReflectionTarget.texture.minFilter = THREE.LinearFilter;
|
||||||
@@ -90,7 +92,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 ? 256 : 1536;
|
const bookShadowMapSize = 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,
|
||||||
@@ -169,7 +171,7 @@ const fastFlipOverlap = 5;
|
|||||||
let activeFlips = [];
|
let activeFlips = [];
|
||||||
let pendingPageFlips = 0;
|
let pendingPageFlips = 0;
|
||||||
|
|
||||||
const paperColor = new THREE.Color(0xf3dfad);
|
const paperColor = new THREE.Color(0xf1ead2);
|
||||||
const inkColor = '#1a1009';
|
const inkColor = '#1a1009';
|
||||||
|
|
||||||
const leftCanvas = createPageCanvas('left');
|
const leftCanvas = createPageCanvas('left');
|
||||||
@@ -179,9 +181,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.LinearFilter;
|
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||||
texture.magFilter = THREE.LinearFilter;
|
texture.magFilter = THREE.LinearFilter;
|
||||||
texture.generateMipmaps = false;
|
texture.generateMipmaps = true;
|
||||||
});
|
});
|
||||||
const leatherTextures = createLeatherTextures();
|
const leatherTextures = createLeatherTextures();
|
||||||
const spineClothTextures = createSpineClothTextures();
|
const spineClothTextures = createSpineClothTextures();
|
||||||
@@ -234,44 +236,44 @@ const materials = {
|
|||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
pageBlock: new THREE.MeshStandardMaterial({
|
pageBlock: new THREE.MeshStandardMaterial({
|
||||||
color: 0xfffbef,
|
color: 0xf4eed8,
|
||||||
map: paperTextures.color,
|
map: paperTextures.color,
|
||||||
normalMap: paperTextures.normal,
|
normalMap: paperTextures.normal,
|
||||||
normalScale: new THREE.Vector2(0.032, 0.032),
|
normalScale: new THREE.Vector2(0.014, 0.014),
|
||||||
roughnessMap: paperTextures.roughness,
|
roughnessMap: paperTextures.roughness,
|
||||||
roughness: 0.88,
|
roughness: 0.88,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
envMapIntensity: 0.06
|
envMapIntensity: 0.06
|
||||||
}),
|
}),
|
||||||
pageEdge: new THREE.MeshStandardMaterial({
|
pageEdge: new THREE.MeshStandardMaterial({
|
||||||
color: 0xfff4cf,
|
color: 0xf0e5c7,
|
||||||
map: paperTextures.edge,
|
map: paperTextures.edge,
|
||||||
normalMap: paperTextures.normal,
|
normalMap: paperTextures.normal,
|
||||||
normalScale: new THREE.Vector2(0.024, 0.024),
|
normalScale: new THREE.Vector2(0.012, 0.012),
|
||||||
roughnessMap: paperTextures.roughness,
|
roughnessMap: paperTextures.roughness,
|
||||||
roughness: 0.94,
|
roughness: 0.94,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
envMapIntensity: 0.05
|
envMapIntensity: 0.05
|
||||||
}),
|
}),
|
||||||
pageSurface: new THREE.MeshStandardMaterial({
|
pageSurface: new THREE.MeshStandardMaterial({
|
||||||
color: 0xfffbf0,
|
color: 0xf5efd9,
|
||||||
map: paperTextures.color,
|
map: paperTextures.color,
|
||||||
normalMap: paperTextures.normal,
|
normalMap: paperTextures.normal,
|
||||||
normalScale: new THREE.Vector2(0.03, 0.03),
|
normalScale: new THREE.Vector2(0.012, 0.012),
|
||||||
roughnessMap: paperTextures.roughness,
|
roughnessMap: paperTextures.roughness,
|
||||||
roughness: 0.9,
|
roughness: 0.9,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
emissive: 0x14110b,
|
emissive: 0x14110b,
|
||||||
emissiveIntensity: 0.025,
|
emissiveIntensity: 0.012,
|
||||||
envMapIntensity: 0.035,
|
envMapIntensity: 0.035,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
flipPageSurface: new THREE.MeshStandardMaterial({
|
flipPageSurface: new THREE.MeshStandardMaterial({
|
||||||
color: 0xfffcf2,
|
color: 0xf5efd9,
|
||||||
roughness: 0.92,
|
roughness: 0.92,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
emissive: 0x100d08,
|
emissive: 0x100d08,
|
||||||
emissiveIntensity: 0.018,
|
emissiveIntensity: 0.01,
|
||||||
envMapIntensity: 0.02,
|
envMapIntensity: 0.02,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
@@ -279,24 +281,24 @@ const materials = {
|
|||||||
color: 0xffffff,
|
color: 0xffffff,
|
||||||
map: leftTexture,
|
map: leftTexture,
|
||||||
normalMap: paperTextures.normal,
|
normalMap: paperTextures.normal,
|
||||||
normalScale: new THREE.Vector2(0.025, 0.025),
|
normalScale: new THREE.Vector2(0.01, 0.01),
|
||||||
roughnessMap: paperTextures.roughness,
|
roughnessMap: paperTextures.roughness,
|
||||||
roughness: 0.86,
|
roughness: 0.86,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
emissive: 0x11100c,
|
emissive: 0x11100c,
|
||||||
emissiveIntensity: 0.035,
|
emissiveIntensity: 0.012,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
rightPage: new THREE.MeshStandardMaterial({
|
rightPage: new THREE.MeshStandardMaterial({
|
||||||
color: 0xffffff,
|
color: 0xffffff,
|
||||||
map: rightTexture,
|
map: rightTexture,
|
||||||
normalMap: paperTextures.normal,
|
normalMap: paperTextures.normal,
|
||||||
normalScale: new THREE.Vector2(0.025, 0.025),
|
normalScale: new THREE.Vector2(0.01, 0.01),
|
||||||
roughnessMap: paperTextures.roughness,
|
roughnessMap: paperTextures.roughness,
|
||||||
roughness: 0.86,
|
roughness: 0.86,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
emissive: 0x11100c,
|
emissive: 0x11100c,
|
||||||
emissiveIntensity: 0.035,
|
emissiveIntensity: 0.012,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
spineCloth: new THREE.MeshStandardMaterial({
|
spineCloth: new THREE.MeshStandardMaterial({
|
||||||
@@ -333,18 +335,19 @@ configureBookShadowReceiver(materials.leather, 0.52);
|
|||||||
configureBookShadowReceiver(materials.hingeLeather, 0.36);
|
configureBookShadowReceiver(materials.hingeLeather, 0.36);
|
||||||
configureBookShadowReceiver(materials.spineBaseLeather, 0.34);
|
configureBookShadowReceiver(materials.spineBaseLeather, 0.34);
|
||||||
configureBookShadowReceiver(materials.coverEdge, 0.28);
|
configureBookShadowReceiver(materials.coverEdge, 0.28);
|
||||||
configureBookShadowReceiver(materials.pageBlock, 0.46);
|
configureBookShadowReceiver(materials.pageBlock, 0.3);
|
||||||
configureBookShadowReceiver(materials.pageEdge, 0.34);
|
configureBookShadowReceiver(materials.pageEdge, 0.24);
|
||||||
configureBookShadowReceiver(materials.pageSurface, 0.34);
|
configureBookShadowReceiver(materials.pageSurface, 0.2);
|
||||||
configureBookShadowReceiver(materials.flipPageSurface, 0.32);
|
configureBookShadowReceiver(materials.flipPageSurface, 0.2);
|
||||||
configureBookShadowReceiver(materials.leftPage, 0.38);
|
configureBookShadowReceiver(materials.leftPage, 0.18);
|
||||||
configureBookShadowReceiver(materials.rightPage, 0.38);
|
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);
|
||||||
|
|
||||||
buildTable();
|
buildTable();
|
||||||
buildLighting();
|
buildLighting();
|
||||||
buildBook();
|
buildBook();
|
||||||
|
notifyBookPageCountChanged();
|
||||||
loadAiRoomReflection();
|
loadAiRoomReflection();
|
||||||
window.BookLabDebug = {
|
window.BookLabDebug = {
|
||||||
textures: generatedTextureCanvases,
|
textures: generatedTextureCanvases,
|
||||||
@@ -629,11 +632,11 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
float sideFill = grazingSide * sideReach;
|
float sideFill = grazingSide * sideReach;
|
||||||
float tableFill = tableReach * (0.16 + underside * 0.22) * (1.0 - upFacing * 0.58);
|
float tableFill = tableReach * (0.16 + underside * 0.22) * (1.0 - upFacing * 0.58);
|
||||||
float pageFill = smoothstep(0.02, 0.2, tableDistance) * (1.0 - smoothstep(0.24, 0.72, tableDistance));
|
float pageFill = smoothstep(0.02, 0.2, tableDistance) * (1.0 - smoothstep(0.24, 0.72, tableDistance));
|
||||||
vec3 tableWarmth = vec3(0.058, 0.039, 0.026) * tableFill;
|
vec3 tableWarmth = vec3(0.042, 0.034, 0.028) * tableFill;
|
||||||
vec3 roomWarmth = vec3(0.04, 0.034, 0.028) * sideFill;
|
vec3 roomWarmth = vec3(0.032, 0.032, 0.03) * sideFill;
|
||||||
vec3 pageWarmth = vec3(0.045, 0.041, 0.034) * pageFill * grazingSide * (1.0 - upFacing * 0.42);
|
vec3 pageWarmth = vec3(0.032, 0.032, 0.029) * pageFill * grazingSide * (1.0 - upFacing * 0.42);
|
||||||
vec3 indirect = tableWarmth + roomWarmth + pageWarmth;
|
vec3 indirect = tableWarmth + roomWarmth + pageWarmth;
|
||||||
return albedo * indirect * mix(1.0, 0.72, shadow);
|
return albedo * indirect * mix(1.0, 0.86, shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
float spineClothThread(float coordinate, float frequency, float sharpness) {
|
float spineClothThread(float coordinate, float frequency, float sharpness) {
|
||||||
@@ -663,8 +666,8 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
sin((uv.y * 211.0 - uv.x * 53.0) * 6.28318530718);
|
sin((uv.y * 211.0 - uv.x * 53.0) * 6.28318530718);
|
||||||
float cloud = sin((uv.x * 17.0 + uv.y * 11.0) * 6.28318530718) *
|
float cloud = sin((uv.x * 17.0 + uv.y * 11.0) * 6.28318530718) *
|
||||||
sin((uv.x * 29.0 - uv.y * 23.0) * 6.28318530718);
|
sin((uv.x * 29.0 - uv.y * 23.0) * 6.28318530718);
|
||||||
float fiber = clamp(fleck * 0.018 + cloud * 0.022, -0.04, 0.05);
|
float fiber = clamp(fleck * 0.008 + cloud * 0.012, -0.02, 0.026);
|
||||||
vec3 paperTint = mix(vec3(0.96, 0.945, 0.89), vec3(1.08, 1.055, 0.98), clamp(0.62 + fiber, 0.0, 1.0));
|
vec3 paperTint = mix(vec3(0.94, 0.925, 0.875), vec3(1.025, 1.015, 0.97), clamp(0.56 + fiber, 0.0, 1.0));
|
||||||
return baseLight * paperTint;
|
return baseLight * paperTint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -681,7 +684,7 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
${isHardcoverPaper ? 'outgoingLight = hardcoverPaperLight(vBookSurfaceUv, outgoingLight);' : ''}
|
${isHardcoverPaper ? 'outgoingLight = hardcoverPaperLight(vBookSurfaceUv, outgoingLight);' : ''}
|
||||||
${isHeadband ? 'outgoingLight = headbandCreviceLight(vBookSurfaceUv, outgoingLight);' : ''}
|
${isHeadband ? 'outgoingLight = headbandCreviceLight(vBookSurfaceUv, outgoingLight);' : ''}
|
||||||
float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength;
|
float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength;
|
||||||
outgoingLight *= mix(vec3(1.0), ${isHeadband ? 'vec3(0.16, 0.095, 0.055)' : 'vec3(0.38, 0.29, 0.2)'}, bookReceiverShadow);
|
outgoingLight *= mix(vec3(1.0), ${isHeadband ? 'vec3(0.16, 0.095, 0.055)' : isHardcoverPaper ? 'vec3(0.68, 0.62, 0.52)' : 'vec3(0.38, 0.29, 0.2)'}, bookReceiverShadow);
|
||||||
outgoingLight += bookLocalBounce(vBookReceiverWorldPosition, normalize(vBookReceiverWorldNormal), bookReceiverShadow, diffuseColor.rgb);
|
outgoingLight += bookLocalBounce(vBookReceiverWorldPosition, normalize(vBookReceiverWorldNormal), bookReceiverShadow, diffuseColor.rgb);
|
||||||
#include <opaque_fragment>`
|
#include <opaque_fragment>`
|
||||||
);
|
);
|
||||||
@@ -715,7 +718,13 @@ function configureScenePostprocessing() {
|
|||||||
sceneAoPass.output = SSAOPass.OUTPUT.Default;
|
sceneAoPass.output = SSAOPass.OUTPUT.Default;
|
||||||
}
|
}
|
||||||
const renderAoPass = sceneAoPass.render.bind(sceneAoPass);
|
const renderAoPass = sceneAoPass.render.bind(sceneAoPass);
|
||||||
|
sceneAoPass.userData = sceneAoPass.userData || {};
|
||||||
|
sceneAoPass.userData.cachedRenderPass = renderAoPass;
|
||||||
sceneAoPass.render = (...args) => {
|
sceneAoPass.render = (...args) => {
|
||||||
|
if (!staticSceneBuffersDirty && activeFlips.length === 0) {
|
||||||
|
renderCachedAoPass(sceneAoPass, ...args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
aoExcludedObjects.forEach((object) => {
|
aoExcludedObjects.forEach((object) => {
|
||||||
object.userData.wasVisibleForAo = object.visible;
|
object.userData.wasVisibleForAo = object.visible;
|
||||||
object.visible = false;
|
object.visible = false;
|
||||||
@@ -1360,6 +1369,7 @@ function configureTableShader(material) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildBook() {
|
function buildBook() {
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
clearActiveFlips();
|
clearActiveFlips();
|
||||||
book.traverse((object) => {
|
book.traverse((object) => {
|
||||||
if (object.isMesh) aoExcludedObjects.delete(object);
|
if (object.isMesh) aoExcludedObjects.delete(object);
|
||||||
@@ -1404,17 +1414,52 @@ function buildBook() {
|
|||||||
});
|
});
|
||||||
currentProceduralBookModel = proceduralBook.model;
|
currentProceduralBookModel = proceduralBook.model;
|
||||||
book.add(proceduralBook.group);
|
book.add(proceduralBook.group);
|
||||||
|
document.documentElement.dataset.webglBookThickness = JSON.stringify(currentProceduralBookModel.thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCachedAoPass(pass, rendererInstance, writeBuffer, readBuffer) {
|
||||||
|
if (!pass.copyMaterial || !pass.renderPass || !pass.blurRenderTarget) {
|
||||||
|
pass.userData?.cachedRenderPass?.(rendererInstance, writeBuffer, readBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pass.output === SSAOPass.OUTPUT?.Default) {
|
||||||
|
pass.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture;
|
||||||
|
pass.copyMaterial.blending = THREE.NoBlending;
|
||||||
|
pass.renderPass(rendererInstance, pass.copyMaterial, pass.renderToScreen ? null : writeBuffer);
|
||||||
|
pass.copyMaterial.uniforms.tDiffuse.value = pass.blurRenderTarget.texture;
|
||||||
|
pass.copyMaterial.blending = THREE.CustomBlending;
|
||||||
|
pass.renderPass(rendererInstance, pass.copyMaterial, pass.renderToScreen ? null : writeBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const texture = pass.output === SSAOPass.OUTPUT?.SSAO
|
||||||
|
? pass.ssaoRenderTarget?.texture
|
||||||
|
: pass.output === SSAOPass.OUTPUT?.Blur
|
||||||
|
? pass.blurRenderTarget.texture
|
||||||
|
: pass.output === SSAOPass.OUTPUT?.Normal
|
||||||
|
? pass.normalRenderTarget?.texture
|
||||||
|
: null;
|
||||||
|
if (!texture) {
|
||||||
|
pass.userData?.cachedRenderPass?.(rendererInstance, writeBuffer, readBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pass.copyMaterial.uniforms.tDiffuse.value = texture;
|
||||||
|
pass.copyMaterial.blending = THREE.NoBlending;
|
||||||
|
pass.renderPass(rendererInstance, pass.copyMaterial, pass.renderToScreen ? null : writeBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function markStaticSceneBuffersDirty() {
|
||||||
|
staticSceneBuffersDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureHardcoverPaperMaterial(material, { useEdgeMap = false } = {}) {
|
function configureHardcoverPaperMaterial(material, { useEdgeMap = false } = {}) {
|
||||||
material.userData.isHardcoverPaper = true;
|
material.userData.isHardcoverPaper = true;
|
||||||
if (!material.map) material.map = useEdgeMap ? paperTextures.edge : paperTextures.color;
|
if (!material.map) material.map = useEdgeMap ? paperTextures.edge : paperTextures.color;
|
||||||
material.normalMap = paperTextures.normal;
|
material.normalMap = paperTextures.normal;
|
||||||
material.normalScale = material.normalScale ?? new THREE.Vector2(0.024, 0.024);
|
material.normalScale = material.normalScale ?? new THREE.Vector2(useEdgeMap ? 0.012 : 0.01, useEdgeMap ? 0.012 : 0.01);
|
||||||
material.roughnessMap = paperTextures.roughness;
|
material.roughnessMap = paperTextures.roughness;
|
||||||
material.roughness = Math.max(material.roughness ?? 0.86, useEdgeMap ? 0.92 : 0.86);
|
material.roughness = Math.max(material.roughness ?? 0.9, useEdgeMap ? 0.94 : 0.9);
|
||||||
material.metalness = 0;
|
material.metalness = 0;
|
||||||
material.envMapIntensity = Math.min(material.envMapIntensity ?? 0.05, 0.06);
|
material.envMapIntensity = Math.min(material.envMapIntensity ?? 0.025, 0.035);
|
||||||
material.needsUpdate = true;
|
material.needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1432,10 +1477,20 @@ function setBookPageCount(value) {
|
|||||||
if (!Number.isFinite(nextPageCount)) return;
|
if (!Number.isFinite(nextPageCount)) return;
|
||||||
bookPageCount = nextPageCount;
|
bookPageCount = nextPageCount;
|
||||||
buildBook();
|
buildBook();
|
||||||
|
notifyBookPageCountChanged();
|
||||||
syncBookControls();
|
syncBookControls();
|
||||||
window.WebGLBookPreferenceBridge?.updatePageCount?.(bookPageCount);
|
window.WebGLBookPreferenceBridge?.updatePageCount?.(bookPageCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyBookPageCountChanged() {
|
||||||
|
document.dispatchEvent(new CustomEvent('webgl-book:page-count-changed', {
|
||||||
|
detail: {
|
||||||
|
pageCount: bookPageCount,
|
||||||
|
model: currentProceduralBookModel
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function stepReadingProgress(pageDelta) {
|
function stepReadingProgress(pageDelta) {
|
||||||
setReadingProgress(readingProgress + pageDelta / Math.max(1, bookPageCount));
|
setReadingProgress(readingProgress + pageDelta / Math.max(1, bookPageCount));
|
||||||
}
|
}
|
||||||
@@ -2414,32 +2469,32 @@ function createHardcoverPaperTextures() {
|
|||||||
const cloudA = Math.sin((nx * 19 + ny * 11) * 6.28318530718);
|
const cloudA = Math.sin((nx * 19 + ny * 11) * 6.28318530718);
|
||||||
const cloudB = Math.sin((nx * 31 - ny * 27) * 6.28318530718);
|
const cloudB = Math.sin((nx * 31 - ny * 27) * 6.28318530718);
|
||||||
const fleck = Math.max(0, 0.5 - Math.abs(pulpA * pulpB));
|
const fleck = Math.max(0, 0.5 - Math.abs(pulpA * pulpB));
|
||||||
return cloudA * cloudB * 0.026 - fleck * 0.035;
|
return cloudA * cloudB * 0.014 - fleck * 0.018;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let y = 0; y < size; y += 1) {
|
for (let y = 0; y < size; y += 1) {
|
||||||
for (let x = 0; x < size; x += 1) {
|
for (let x = 0; x < size; x += 1) {
|
||||||
const index = (y * size + x) * 4;
|
const index = (y * size + x) * 4;
|
||||||
const fiber = fiberAt(x, y);
|
const fiber = fiberAt(x, y);
|
||||||
const warmth = 0.97 + 0.018 * Math.sin(x * 0.017 + y * 0.003) + 0.012 * Math.sin(y * 0.041);
|
const warmth = 0.985 + 0.008 * Math.sin(x * 0.017 + y * 0.003) + 0.006 * Math.sin(y * 0.041);
|
||||||
const shade = THREE.MathUtils.clamp(0.975 + fiber, 0.88, 1.0);
|
const shade = THREE.MathUtils.clamp(0.985 + fiber, 0.92, 1.0);
|
||||||
colorImage.data[index] = Math.round(255 * shade * warmth);
|
colorImage.data[index] = Math.round(246 * shade * warmth);
|
||||||
colorImage.data[index + 1] = Math.round(251 * shade * warmth);
|
colorImage.data[index + 1] = Math.round(239 * shade * warmth);
|
||||||
colorImage.data[index + 2] = Math.round(235 * shade);
|
colorImage.data[index + 2] = Math.round(216 * shade);
|
||||||
colorImage.data[index + 3] = 255;
|
colorImage.data[index + 3] = 255;
|
||||||
|
|
||||||
const linePhase = (y + Math.sin(x * 0.021) * 4) % 34;
|
const linePhase = (y + Math.sin(x * 0.021) * 4) % 34;
|
||||||
const line = linePhase < 1.2 ? 0.72 : linePhase < 2.1 ? 0.82 : 1;
|
const line = linePhase < 1.2 ? 0.72 : linePhase < 2.1 ? 0.82 : 1;
|
||||||
edgeImage.data[index] = Math.round(255 * shade * line);
|
edgeImage.data[index] = Math.round(240 * shade * line);
|
||||||
edgeImage.data[index + 1] = Math.round(244 * shade * line);
|
edgeImage.data[index + 1] = Math.round(230 * shade * line);
|
||||||
edgeImage.data[index + 2] = Math.round(207 * shade * line);
|
edgeImage.data[index + 2] = Math.round(194 * shade * line);
|
||||||
edgeImage.data[index + 3] = 255;
|
edgeImage.data[index + 3] = 255;
|
||||||
|
|
||||||
const hLeft = fiberAt((x - 1 + size) % size, y);
|
const hLeft = fiberAt((x - 1 + size) % size, y);
|
||||||
const hRight = fiberAt((x + 1) % size, y);
|
const hRight = fiberAt((x + 1) % size, y);
|
||||||
const hDown = fiberAt(x, (y - 1 + size) % size);
|
const hDown = fiberAt(x, (y - 1 + size) % size);
|
||||||
const hUp = fiberAt(x, (y + 1) % size);
|
const hUp = fiberAt(x, (y + 1) % size);
|
||||||
const normal = new THREE.Vector3((hLeft - hRight) * 3.2, (hDown - hUp) * 3.2, 1).normalize();
|
const normal = new THREE.Vector3((hLeft - hRight) * 1.45, (hDown - hUp) * 1.45, 1).normalize();
|
||||||
normalImage.data[index] = Math.round((normal.x * 0.5 + 0.5) * 255);
|
normalImage.data[index] = Math.round((normal.x * 0.5 + 0.5) * 255);
|
||||||
normalImage.data[index + 1] = Math.round((normal.y * 0.5 + 0.5) * 255);
|
normalImage.data[index + 1] = Math.round((normal.y * 0.5 + 0.5) * 255);
|
||||||
normalImage.data[index + 2] = Math.round((normal.z * 0.5 + 0.5) * 255);
|
normalImage.data[index + 2] = Math.round((normal.z * 0.5 + 0.5) * 255);
|
||||||
@@ -2567,6 +2622,7 @@ function tintAmbientFromCanvas(canvas) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
const width = Math.max(1, window.innerWidth);
|
const width = Math.max(1, window.innerWidth);
|
||||||
const height = Math.max(1, window.innerHeight);
|
const height = Math.max(1, window.innerHeight);
|
||||||
renderer.setSize(width, height, false);
|
renderer.setSize(width, height, false);
|
||||||
@@ -2687,6 +2743,19 @@ function updateCameraRig(deltaSeconds) {
|
|||||||
cameraRig.target.z + Math.cos(cameraRig.yaw) * horizontalRadius
|
cameraRig.target.z + Math.cos(cameraRig.yaw) * horizontalRadius
|
||||||
);
|
);
|
||||||
camera.lookAt(cameraRig.target);
|
camera.lookAt(cameraRig.target);
|
||||||
|
const signature = [
|
||||||
|
camera.position.x,
|
||||||
|
camera.position.y,
|
||||||
|
camera.position.z,
|
||||||
|
cameraRig.target.x,
|
||||||
|
cameraRig.target.y,
|
||||||
|
cameraRig.target.z,
|
||||||
|
camera.aspect
|
||||||
|
].map((value) => value.toFixed(4)).join('|');
|
||||||
|
if (signature !== lastStaticCameraSignature) {
|
||||||
|
lastStaticCameraSignature = signature;
|
||||||
|
markStaticSceneBuffersDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCandleShadowUniforms() {
|
function updateCandleShadowUniforms() {
|
||||||
@@ -2884,16 +2953,17 @@ function animate(now = performance.now()) {
|
|||||||
waxShader.uniforms.waxLightPower.value = THREE.MathUtils.clamp(pulse * object.userData.baseIntensity * 0.42, 0.35, 1.6);
|
waxShader.uniforms.waxLightPower.value = THREE.MathUtils.clamp(pulse * object.userData.baseIntensity * 0.42, 0.35, 1.6);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const hadActiveFlips = activeFlips.length > 0;
|
||||||
updateActiveFlips(performance.now());
|
updateActiveFlips(performance.now());
|
||||||
|
if (hadActiveFlips) markStaticSceneBuffersDirty();
|
||||||
updateCandleShadowUniforms();
|
updateCandleShadowUniforms();
|
||||||
renderedFrameCount += 1;
|
renderedFrameCount += 1;
|
||||||
const shadowStartedAt = performance.now();
|
const shadowStartedAt = performance.now();
|
||||||
if (!isAppIntegrationMode || renderedFrameCount % 6 === 1 || activeFlips.length > 0) {
|
updateBookShadowMaps();
|
||||||
updateBookShadowMaps();
|
|
||||||
}
|
|
||||||
lastFrameTiming.shadows = performance.now() - shadowStartedAt;
|
lastFrameTiming.shadows = performance.now() - shadowStartedAt;
|
||||||
const reflectionStartedAt = performance.now();
|
const reflectionStartedAt = performance.now();
|
||||||
if (!isAppIntegrationMode || renderedFrameCount % 4 === 1 || cameraRig.navigationActive || activeFlips.length > 0) {
|
const refreshStaticSceneBuffers = staticSceneBuffersDirty || activeFlips.length > 0;
|
||||||
|
if (refreshStaticSceneBuffers) {
|
||||||
updateTableReflection();
|
updateTableReflection();
|
||||||
}
|
}
|
||||||
lastFrameTiming.reflection = performance.now() - reflectionStartedAt;
|
lastFrameTiming.reflection = performance.now() - reflectionStartedAt;
|
||||||
@@ -2909,6 +2979,9 @@ function animate(now = performance.now()) {
|
|||||||
}
|
}
|
||||||
lastFrameTiming.render = performance.now() - renderStartedAt;
|
lastFrameTiming.render = performance.now() - renderStartedAt;
|
||||||
lastFrameTiming.total = lastFrameTiming.shadows + lastFrameTiming.reflection + lastFrameTiming.render;
|
lastFrameTiming.total = lastFrameTiming.shadows + lastFrameTiming.reflection + lastFrameTiming.render;
|
||||||
|
if (refreshStaticSceneBuffers && activeFlips.length === 0) {
|
||||||
|
staticSceneBuffersDirty = false;
|
||||||
|
}
|
||||||
window.BookLabDebug.renderedFrames += 1;
|
window.BookLabDebug.renderedFrames += 1;
|
||||||
window.BookLabDebug.ready = true;
|
window.BookLabDebug.ready = true;
|
||||||
fpsWindowFrames += 1;
|
fpsWindowFrames += 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user