Checkpoint hinge-relative book geometry
This commit is contained in:
@@ -72,6 +72,7 @@ const PAGE_DEPTH = 2.24;
|
|||||||
const COVER_OVERHANG = 0.13;
|
const COVER_OVERHANG = 0.13;
|
||||||
const COVER_SUPPORT_OVERHANG = 0.055;
|
const COVER_SUPPORT_OVERHANG = 0.055;
|
||||||
const HINGE_INSET = 0.07;
|
const HINGE_INSET = 0.07;
|
||||||
|
const MAX_SUPPORTED_STACK_RISE = PAGE_WIDTH * 0.5;
|
||||||
const SUPPORT_ANGLE_STEPS = 720;
|
const SUPPORT_ANGLE_STEPS = 720;
|
||||||
const SUPPORT_ANGLE_CANDIDATES = Array.from({ length: SUPPORT_ANGLE_STEPS }, (_, sample) => {
|
const SUPPORT_ANGLE_CANDIDATES = Array.from({ length: SUPPORT_ANGLE_STEPS }, (_, sample) => {
|
||||||
const angle = sample / SUPPORT_ANGLE_STEPS * Math.PI * 2;
|
const angle = sample / SUPPORT_ANGLE_STEPS * Math.PI * 2;
|
||||||
@@ -211,12 +212,13 @@ function rebuildBook() {
|
|||||||
const bundleCount = Math.max(4, Math.round(pageCount / 10));
|
const bundleCount = Math.max(4, Math.round(pageCount / 10));
|
||||||
const spineWidth = calculateSpineWidth(bundleCount);
|
const spineWidth = calculateSpineWidth(bundleCount);
|
||||||
const leftCount = calculateLeftBundleCount(bundleCount);
|
const leftCount = calculateLeftBundleCount(bundleCount);
|
||||||
|
const hingeX = spineWidth * 0.5 + HINGE_INSET;
|
||||||
const bundleSpacing = calculateBundleSpacing(bundleCount, spineWidth, leftCount);
|
const bundleSpacing = calculateBundleSpacing(bundleCount, spineWidth, leftCount);
|
||||||
activeSpineHalf = spineWidth * 0.5;
|
activeSpineHalf = spineWidth * 0.5;
|
||||||
const lines = simulatePageLines(bundleCount, pageWidth, spineWidth, bundleSpacing, leftCount);
|
const lines = simulatePageLines(bundleCount, pageWidth, spineWidth, hingeX, bundleSpacing, leftCount);
|
||||||
lastLengthError = measureLineLengthError(lines, pageWidth);
|
lastLengthError = measureLineLengthError(lines, pageWidth);
|
||||||
lastSpacingError = measureStackSpacingError(lines, bundleSpacing);
|
lastSpacingError = measureStackSpacingError(lines, bundleSpacing);
|
||||||
lastBookModel = { coverDepth, pageWidth, pageDepth, bundleCount, spineWidth, bundleSpacing, lines };
|
lastBookModel = { coverDepth, pageWidth, pageDepth, bundleCount, spineWidth, hingeX, bundleSpacing, lines };
|
||||||
|
|
||||||
addCoverAssembly(pageWidth, coverDepth, coverThickness, spineWidth);
|
addCoverAssembly(pageWidth, coverDepth, coverThickness, spineWidth);
|
||||||
addClothSpine(pageDepth, spineWidth);
|
addClothSpine(pageDepth, spineWidth);
|
||||||
@@ -241,8 +243,8 @@ function addCoverAssembly(pageWidth, depth, thickness, spineWidth) {
|
|||||||
|
|
||||||
function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth) {
|
function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth) {
|
||||||
const spineHalf = spineWidth * 0.5;
|
const spineHalf = spineWidth * 0.5;
|
||||||
const outerX = spineHalf + pageWidth + COVER_OVERHANG;
|
|
||||||
const hingeX = spineHalf + HINGE_INSET;
|
const hingeX = spineHalf + HINGE_INSET;
|
||||||
|
const outerX = hingeX + pageWidth + COVER_OVERHANG;
|
||||||
const outerTopY = BOOK_PROFILE.tableY + thickness;
|
const outerTopY = BOOK_PROFILE.tableY + thickness;
|
||||||
const connectionTopY = BOOK_PROFILE.raisedHingeY;
|
const connectionTopY = BOOK_PROFILE.raisedHingeY;
|
||||||
const spineTopY = BOOK_PROFILE.tableY + thickness;
|
const spineTopY = BOOK_PROFILE.tableY + thickness;
|
||||||
@@ -378,12 +380,9 @@ function calculateMaximumPageCount() {
|
|||||||
function isBundleCountReachable(bundleCount) {
|
function isBundleCountReachable(bundleCount) {
|
||||||
const spineWidth = calculateSpineWidth(bundleCount);
|
const spineWidth = calculateSpineWidth(bundleCount);
|
||||||
const bundleSpacing = calculateBundleSpacing(bundleCount, spineWidth, 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 anchor = spineCurvePoint(1, spineWidth);
|
||||||
const requiredDistance = Math.hypot(target.x - anchor.x, target.y - anchor.y);
|
const topLineY = BOOK_PROFILE.coverThickness + BOOK_PROFILE.paperContactOffset + (bundleCount - 1) * bundleSpacing;
|
||||||
const verticalRise = target.y - anchor.y;
|
return topLineY - anchor.y <= MAX_SUPPORTED_STACK_RISE;
|
||||||
return requiredDistance <= PAGE_WIDTH && verticalRise <= PAGE_WIDTH * 0.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateBundleSpacing(bundleCount, spineWidth, leftCount) {
|
function calculateBundleSpacing(bundleCount, spineWidth, leftCount) {
|
||||||
@@ -409,7 +408,7 @@ function calculateLeftBundleCount(bundleCount) {
|
|||||||
return THREE.MathUtils.clamp(Math.round(bundleCount * readingProgress), 0, bundleCount);
|
return THREE.MathUtils.clamp(Math.round(bundleCount * readingProgress), 0, bundleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function simulatePageLines(bundleCount, pageWidth, spineWidth, bundleSpacing, leftCount) {
|
function simulatePageLines(bundleCount, pageWidth, spineWidth, hingeX, bundleSpacing, leftCount) {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
const segments = 24;
|
const segments = 24;
|
||||||
const stepLength = pageWidth / segments;
|
const stepLength = pageWidth / segments;
|
||||||
@@ -453,7 +452,7 @@ function simulatePageLines(bundleCount, pageWidth, spineWidth, bundleSpacing, le
|
|||||||
let lowerLine = null;
|
let lowerLine = null;
|
||||||
sideEntries.forEach((entry, rank) => {
|
sideEntries.forEach((entry, rank) => {
|
||||||
const anchor = spineCurvePoint(entry.t, spineWidth);
|
const anchor = spineCurvePoint(entry.t, spineWidth);
|
||||||
const target = restingTarget(side, pageWidth, rank, sideEntries.length, bundleSpacing);
|
const target = restingTarget(side, pageWidth, hingeX, rank, sideEntries.length, bundleSpacing);
|
||||||
const points = buildSupportSolvedLine(anchor, target, lowerLine, side, segments, stepLength, bundleCount, 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 };
|
const line = { index: entry.index, t: entry.t, side, anchor, points, endpoint: points[points.length - 1], isHairPage: entry.isHairPage === true };
|
||||||
lines.push(line);
|
lines.push(line);
|
||||||
@@ -541,10 +540,10 @@ function initialPageLine(anchor, target, segments) {
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
function restingTarget(side, pageWidth, rank, sideCount, bundleSpacing) {
|
function restingTarget(side, pageWidth, hingeX, rank, sideCount, bundleSpacing) {
|
||||||
const local = sideCount <= 1 ? 0 : rank / (sideCount - 1);
|
const local = sideCount <= 1 ? 0 : rank / (sideCount - 1);
|
||||||
const foreCurve = 0.11 * Math.sin(Math.PI * local);
|
const foreCurve = 0.11 * Math.sin(Math.PI * local);
|
||||||
const x = side * (pageWidth - foreCurve);
|
const x = side * (hingeX + pageWidth - foreCurve);
|
||||||
const y = BOOK_PROFILE.coverThickness + BOOK_PROFILE.paperContactOffset + rank * bundleSpacing + 0.002 * Math.sin(Math.PI * local);
|
const y = BOOK_PROFILE.coverThickness + BOOK_PROFILE.paperContactOffset + rank * bundleSpacing + 0.002 * Math.sin(Math.PI * local);
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
@@ -825,7 +824,7 @@ function coverTopYAtX(x) {
|
|||||||
const ax = Math.abs(x);
|
const ax = Math.abs(x);
|
||||||
const spineHalf = currentSpineHalf();
|
const spineHalf = currentSpineHalf();
|
||||||
const hingeX = spineHalf + HINGE_INSET;
|
const hingeX = spineHalf + HINGE_INSET;
|
||||||
const outerX = spineHalf + PAGE_WIDTH + COVER_SUPPORT_OVERHANG;
|
const outerX = hingeX + PAGE_WIDTH + COVER_SUPPORT_OVERHANG;
|
||||||
if (ax <= spineHalf) return BOOK_PROFILE.coverThickness;
|
if (ax <= spineHalf) return BOOK_PROFILE.coverThickness;
|
||||||
if (ax <= hingeX) {
|
if (ax <= hingeX) {
|
||||||
const t = (ax - spineHalf) / (hingeX - spineHalf);
|
const t = (ax - spineHalf) / (hingeX - spineHalf);
|
||||||
@@ -865,7 +864,7 @@ function createSinglePageBodyLines(line) {
|
|||||||
function createStackBodyMaterials(lines, side) {
|
function createStackBodyMaterials(lines, side) {
|
||||||
const baseColor = side < 0 ? '#d8c7a4' : '#e7d6b4';
|
const baseColor = side < 0 ? '#d8c7a4' : '#e7d6b4';
|
||||||
const lineColor = '#9a8058';
|
const lineColor = '#9a8058';
|
||||||
const layerTexture = createStackLayerTexture(lines.length, baseColor, lineColor);
|
const layerTexture = createStackLayerTexture(lastBookModel.bundleCount, baseColor, lineColor);
|
||||||
return [
|
return [
|
||||||
new THREE.MeshBasicMaterial({ map: layerTexture, side: THREE.DoubleSide }),
|
new THREE.MeshBasicMaterial({ map: layerTexture, side: THREE.DoubleSide }),
|
||||||
new THREE.MeshBasicMaterial({ map: layerTexture, side: THREE.DoubleSide }),
|
new THREE.MeshBasicMaterial({ map: layerTexture, side: THREE.DoubleSide }),
|
||||||
@@ -873,7 +872,7 @@ function createStackBodyMaterials(lines, side) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStackLayerTexture(lineCount, baseColor, lineColor) {
|
function createStackLayerTexture(bundleCount, baseColor, lineColor) {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = 2048;
|
canvas.width = 2048;
|
||||||
canvas.height = 1024;
|
canvas.height = 1024;
|
||||||
@@ -884,8 +883,8 @@ function createStackLayerTexture(lineCount, baseColor, lineColor) {
|
|||||||
context.globalAlpha = 0.95;
|
context.globalAlpha = 0.95;
|
||||||
context.lineWidth = 4.2;
|
context.lineWidth = 4.2;
|
||||||
context.lineCap = 'square';
|
context.lineCap = 'square';
|
||||||
for (let row = 0; row < lineCount; row += 1) {
|
for (let row = 0; row < bundleCount; row += 1) {
|
||||||
const v = lineCount <= 1 ? 0.5 : row / (lineCount - 1);
|
const v = bundleCount <= 1 ? 0.5 : row / (bundleCount - 1);
|
||||||
const y = (1 - v) * canvas.height;
|
const y = (1 - v) * canvas.height;
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(-8, y);
|
context.moveTo(-8, y);
|
||||||
@@ -908,15 +907,18 @@ function createLoftedLineBody(lines, depth) {
|
|||||||
const uvs = [];
|
const uvs = [];
|
||||||
const indices = [];
|
const indices = [];
|
||||||
const smoothLines = lines.map((line) => line.points);
|
const smoothLines = lines.map((line) => line.points);
|
||||||
|
const bundleCount = lastBookModel?.bundleCount ?? smoothLines.length;
|
||||||
const push = (point, z, uv) => {
|
const push = (point, z, uv) => {
|
||||||
const index = positions.length / 3;
|
const index = positions.length / 3;
|
||||||
positions.push(point.x, point.y, z);
|
positions.push(point.x, point.y, z);
|
||||||
uvs.push(uv.u, uv.v);
|
uvs.push(uv.u, uv.v);
|
||||||
return index;
|
return index;
|
||||||
};
|
};
|
||||||
const rowUv = (row) => (
|
const rowUv = (row) => {
|
||||||
smoothLines.length <= 1 ? 0.5 : row / (smoothLines.length - 1)
|
const line = lines[row];
|
||||||
);
|
const index = line.isHairPage ? (line.side < 0 ? 0 : bundleCount - 1) : line.index;
|
||||||
|
return bundleCount <= 1 ? 0.5 : index / (bundleCount - 1);
|
||||||
|
};
|
||||||
const colUv = (points, col) => (
|
const colUv = (points, col) => (
|
||||||
points.length <= 1 ? 0.5 : col / (points.length - 1)
|
points.length <= 1 ? 0.5 : col / (points.length - 1)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -74,6 +74,6 @@
|
|||||||
<button id="fast_forward" type="button">Fast Forward</button>
|
<button id="fast_forward" type="button">Fast Forward</button>
|
||||||
<output id="flip_count">0 / 10</output>
|
<output id="flip_count">0 / 10</output>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/js/webgl-book-shape-lab.js?v=max-reachable-cover-width-2"></script>
|
<script type="module" src="/js/webgl-book-shape-lab.js?v=hinge-relative-stacks-1"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user