From ecc44130140306fb9020a0cd5d1ff03755589065 Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Fri, 5 Jun 2026 13:13:00 +0200 Subject: [PATCH] Checkpoint before book geometry fixes --- public/js/webgl-book-shape-lab.js | 35 ++++++++++++++++++------------- public/webgl-book-shape-lab.html | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/public/js/webgl-book-shape-lab.js b/public/js/webgl-book-shape-lab.js index 63f51e0..d75111f 100644 --- a/public/js/webgl-book-shape-lab.js +++ b/public/js/webgl-book-shape-lab.js @@ -68,11 +68,12 @@ const OPEN_SEAM_GAP = 0.003; const PAGE_COUNT_MIN = 40; const PAGE_COUNT_STEP = 10; const PAGE_WIDTH = 1.62; +const PAGE_SPLINE_LENGTH = 1.955; +const PAGE_LINE_SEGMENTS = 24; const PAGE_DEPTH = 2.24; const COVER_OVERHANG = 0.13; const COVER_SUPPORT_OVERHANG = 0.055; const HINGE_INSET = 0.07; -const MAX_SUPPORTED_STACK_RISE = PAGE_WIDTH * 0.5; 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; @@ -208,17 +209,19 @@ function rebuildBook() { const coverDepth = 2.30; const coverThickness = BOOK_PROFILE.coverThickness; const pageWidth = PAGE_WIDTH; + const pageSplineLength = PAGE_SPLINE_LENGTH; const pageDepth = PAGE_DEPTH; const bundleCount = Math.max(4, Math.round(pageCount / 10)); const spineWidth = calculateSpineWidth(bundleCount); const leftCount = calculateLeftBundleCount(bundleCount); const hingeX = spineWidth * 0.5 + HINGE_INSET; + const foreEdgeX = spineWidth * 0.5 + pageWidth; const bundleSpacing = calculateBundleSpacing(bundleCount, spineWidth, leftCount); activeSpineHalf = spineWidth * 0.5; - const lines = simulatePageLines(bundleCount, pageWidth, spineWidth, hingeX, bundleSpacing, leftCount); - lastLengthError = measureLineLengthError(lines, pageWidth); + const lines = simulatePageLines(bundleCount, pageWidth, pageSplineLength, spineWidth, foreEdgeX, bundleSpacing, leftCount); + lastLengthError = measureLineLengthError(lines, pageSplineLength); lastSpacingError = measureStackSpacingError(lines, bundleSpacing); - lastBookModel = { coverDepth, pageWidth, pageDepth, bundleCount, spineWidth, hingeX, bundleSpacing, lines }; + lastBookModel = { coverDepth, pageWidth, pageSplineLength, pageDepth, bundleCount, spineWidth, hingeX, foreEdgeX, bundleSpacing, lines }; addCoverAssembly(pageWidth, coverDepth, coverThickness, spineWidth); addClothSpine(pageDepth, spineWidth); @@ -244,7 +247,7 @@ function addCoverAssembly(pageWidth, depth, thickness, spineWidth) { function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth) { const spineHalf = spineWidth * 0.5; const hingeX = spineHalf + HINGE_INSET; - const outerX = hingeX + pageWidth + COVER_OVERHANG; + const outerX = spineHalf + pageWidth + COVER_OVERHANG; const outerTopY = BOOK_PROFILE.tableY + thickness; const connectionTopY = BOOK_PROFILE.raisedHingeY; const spineTopY = BOOK_PROFILE.tableY + thickness; @@ -380,9 +383,13 @@ function calculateMaximumPageCount() { function isBundleCountReachable(bundleCount) { const spineWidth = calculateSpineWidth(bundleCount); const bundleSpacing = calculateBundleSpacing(bundleCount, spineWidth, bundleCount); + const foreEdgeX = spineWidth * 0.5 + PAGE_WIDTH; + const target = restingTarget(1, foreEdgeX, bundleCount - 1, bundleCount, bundleSpacing); const anchor = spineCurvePoint(1, spineWidth); - const topLineY = BOOK_PROFILE.coverThickness + BOOK_PROFILE.paperContactOffset + (bundleCount - 1) * bundleSpacing; - return topLineY - anchor.y <= MAX_SUPPORTED_STACK_RISE; + const chordLength = Math.hypot(target.x - anchor.x, target.y - anchor.y); + const solverSlack = PAGE_SPLINE_LENGTH - chordLength; + const minimumSlack = PAGE_SPLINE_LENGTH / (PAGE_LINE_SEGMENTS + 1); + return solverSlack >= minimumSlack; } function calculateBundleSpacing(bundleCount, spineWidth, leftCount) { @@ -408,10 +415,10 @@ function calculateLeftBundleCount(bundleCount) { return THREE.MathUtils.clamp(Math.round(bundleCount * readingProgress), 0, bundleCount); } -function simulatePageLines(bundleCount, pageWidth, spineWidth, hingeX, bundleSpacing, leftCount) { +function simulatePageLines(bundleCount, pageWidth, pageSplineLength, spineWidth, foreEdgeX, bundleSpacing, leftCount) { const lines = []; - const segments = 24; - const stepLength = pageWidth / segments; + const segments = PAGE_LINE_SEGMENTS; + const stepLength = pageSplineLength / segments; const entries = []; const spineArc = buildSpineArcSamples(spineWidth); const rightCount = bundleCount - leftCount; @@ -452,7 +459,7 @@ function simulatePageLines(bundleCount, pageWidth, spineWidth, hingeX, bundleSpa let lowerLine = null; sideEntries.forEach((entry, rank) => { const anchor = spineCurvePoint(entry.t, spineWidth); - const target = restingTarget(side, pageWidth, hingeX, rank, sideEntries.length, bundleSpacing); + const target = restingTarget(side, foreEdgeX, rank, sideEntries.length, bundleSpacing); const points = buildSupportSolvedLine(anchor, target, lowerLine, side, segments, stepLength, bundleCount, bundleSpacing); const line = { index: entry.index, t: entry.t, side, anchor, points, endpoint: points[points.length - 1], isHairPage: entry.isHairPage === true }; lines.push(line); @@ -540,10 +547,10 @@ function initialPageLine(anchor, target, segments) { return points; } -function restingTarget(side, pageWidth, hingeX, rank, sideCount, bundleSpacing) { +function restingTarget(side, foreEdgeX, rank, sideCount, bundleSpacing) { const local = sideCount <= 1 ? 0 : rank / (sideCount - 1); const foreCurve = 0.11 * Math.sin(Math.PI * local); - const x = side * (hingeX + pageWidth - foreCurve); + const x = side * (foreEdgeX - foreCurve); const y = BOOK_PROFILE.coverThickness + BOOK_PROFILE.paperContactOffset + rank * bundleSpacing + 0.002 * Math.sin(Math.PI * local); return { x, y }; } @@ -824,7 +831,7 @@ function coverTopYAtX(x) { const ax = Math.abs(x); const spineHalf = currentSpineHalf(); const hingeX = spineHalf + HINGE_INSET; - const outerX = hingeX + PAGE_WIDTH + COVER_SUPPORT_OVERHANG; + 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 0238ebb..f48a61b 100644 --- a/public/webgl-book-shape-lab.html +++ b/public/webgl-book-shape-lab.html @@ -74,6 +74,6 @@ 0 / 10 - +