From fc38dca7cf594e7fe624fde77f61b240da44f5af Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Fri, 5 Jun 2026 12:06:38 +0200 Subject: [PATCH] Checkpoint max page cover geometry --- public/js/webgl-book-shape-lab.js | 47 +++++++++++++++++++++++++------ public/webgl-book-shape-lab.html | 2 +- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/public/js/webgl-book-shape-lab.js b/public/js/webgl-book-shape-lab.js index 710c2a6..29c60d1 100644 --- a/public/js/webgl-book-shape-lab.js +++ b/public/js/webgl-book-shape-lab.js @@ -65,6 +65,13 @@ const FAST_FLIP_DURATION = 900; const FAST_FLIP_COUNT = 10; const FAST_FLIP_OVERLAP = 5; const OPEN_SEAM_GAP = 0.003; +const PAGE_COUNT_MIN = 40; +const PAGE_COUNT_STEP = 10; +const PAGE_WIDTH = 1.62; +const PAGE_DEPTH = 2.24; +const COVER_OVERHANG = 0.13; +const COVER_SUPPORT_OVERHANG = 0.055; +const HINGE_INSET = 0.07; const SUPPORT_ANGLE_STEPS = 720; const SUPPORT_ANGLE_CANDIDATES = Array.from({ length: SUPPORT_ANGLE_STEPS }, (_, sample) => { const angle = sample / SUPPORT_ANGLE_STEPS * Math.PI * 2; @@ -75,6 +82,8 @@ const SUPPORT_ANGLE_CANDIDATES = Array.from({ length: SUPPORT_ANGLE_STEPS }, (_, }; }); +const maximumPageCount = calculateMaximumPageCount(); + let readingProgress = readInitialProgress(); let pageCount = readInitialPageCount(); let lastLengthError = 0; @@ -83,6 +92,7 @@ let lastBookModel = null; let activeSpineHalf = 0.08; let activeFlips = []; let pendingPageFlips = 0; +pageCountInput.max = String(maximumPageCount); progressInput.value = readingProgress.toFixed(3); progressValue.value = readingProgress.toFixed(2); pageCountInput.value = String(pageCount); @@ -165,7 +175,7 @@ function readInitialPageCount() { } function snapPageCount(value) { - return THREE.MathUtils.clamp(Math.round(value / 10) * 10, 40, 600); + return THREE.MathUtils.clamp(Math.round(value / PAGE_COUNT_STEP) * PAGE_COUNT_STEP, PAGE_COUNT_MIN, maximumPageCount); } function setReadingProgress(value) { @@ -196,8 +206,8 @@ function rebuildBook() { const coverDepth = 2.30; const coverThickness = BOOK_PROFILE.coverThickness; - const pageWidth = 1.62; - const pageDepth = 2.24; + const pageWidth = PAGE_WIDTH; + const pageDepth = PAGE_DEPTH; const bundleCount = Math.max(4, Math.round(pageCount / 10)); const spineWidth = calculateSpineWidth(bundleCount); const leftCount = calculateLeftBundleCount(bundleCount); @@ -230,11 +240,9 @@ function addCoverAssembly(pageWidth, depth, thickness, spineWidth) { } function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth) { - const overhang = 0.13; const spineHalf = spineWidth * 0.5; - const hingeInset = 0.07; - const outerX = pageWidth + overhang; - const hingeX = spineHalf + hingeInset; + const outerX = spineHalf + pageWidth + COVER_OVERHANG; + const hingeX = spineHalf + HINGE_INSET; const outerTopY = BOOK_PROFILE.tableY + thickness; const connectionTopY = BOOK_PROFILE.raisedHingeY; const spineTopY = BOOK_PROFILE.tableY + thickness; @@ -357,6 +365,27 @@ function calculateSpineWidth(bundleCount) { return high; } +function calculateMaximumPageCount() { + let maximum = PAGE_COUNT_MIN; + for (let candidate = PAGE_COUNT_MIN; candidate <= 1000; candidate += PAGE_COUNT_STEP) { + const bundleCount = Math.max(4, Math.round(candidate / 10)); + if (!isBundleCountReachable(bundleCount)) break; + maximum = candidate; + } + return maximum; +} + +function isBundleCountReachable(bundleCount) { + const spineWidth = calculateSpineWidth(bundleCount); + const bundleSpacing = calculateBundleSpacing(bundleCount, spineWidth, bundleCount); + const topRank = bundleCount - 1; + const target = restingTarget(1, PAGE_WIDTH, topRank, bundleCount, bundleSpacing); + const anchor = spineCurvePoint(1, spineWidth); + const requiredDistance = Math.hypot(target.x - anchor.x, target.y - anchor.y); + const verticalRise = target.y - anchor.y; + return requiredDistance <= PAGE_WIDTH && verticalRise <= PAGE_WIDTH * 0.5; +} + function calculateBundleSpacing(bundleCount, spineWidth, leftCount) { const rightCount = bundleCount - leftCount; const stackIntervals = Math.max(0, leftCount - 1) + Math.max(0, rightCount - 1); @@ -795,8 +824,8 @@ function upwardNormalAt(points, index) { function coverTopYAtX(x) { const ax = Math.abs(x); const spineHalf = currentSpineHalf(); - const hingeX = spineHalf + 0.07; - const outerX = 1.62 + 0.055; + const hingeX = spineHalf + HINGE_INSET; + const outerX = spineHalf + PAGE_WIDTH + COVER_SUPPORT_OVERHANG; if (ax <= spineHalf) return BOOK_PROFILE.coverThickness; if (ax <= hingeX) { const t = (ax - spineHalf) / (hingeX - spineHalf); diff --git a/public/webgl-book-shape-lab.html b/public/webgl-book-shape-lab.html index 14a5fcd..aebb963 100644 --- a/public/webgl-book-shape-lab.html +++ b/public/webgl-book-shape-lab.html @@ -74,6 +74,6 @@ 0 / 10 - +