Checkpoint clean procedural book profile
This commit is contained in:
@@ -35,7 +35,7 @@ guide.position.y = -0.12;
|
|||||||
scene.add(guide);
|
scene.add(guide);
|
||||||
|
|
||||||
const materials = {
|
const materials = {
|
||||||
cover: new THREE.MeshBasicMaterial({ color: 0x2c1810 }),
|
cover: new THREE.MeshBasicMaterial({ color: 0x2c1810, side: THREE.DoubleSide }),
|
||||||
spine: new THREE.MeshBasicMaterial({ color: 0x9c1f1f, side: THREE.DoubleSide }),
|
spine: new THREE.MeshBasicMaterial({ color: 0x9c1f1f, side: THREE.DoubleSide }),
|
||||||
pagesLeft: new THREE.MeshBasicMaterial({ color: 0xd8c7a4, side: THREE.DoubleSide }),
|
pagesLeft: new THREE.MeshBasicMaterial({ color: 0xd8c7a4, side: THREE.DoubleSide }),
|
||||||
pagesRight: new THREE.MeshBasicMaterial({ color: 0xe7d6b4, side: THREE.DoubleSide }),
|
pagesRight: new THREE.MeshBasicMaterial({ color: 0xe7d6b4, side: THREE.DoubleSide }),
|
||||||
@@ -44,6 +44,13 @@ const materials = {
|
|||||||
hinge: new THREE.MeshBasicMaterial({ color: 0x2b0808 })
|
hinge: new THREE.MeshBasicMaterial({ color: 0x2b0808 })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BOOK_PROFILE = {
|
||||||
|
tableY: 0,
|
||||||
|
coverThickness: 0.03,
|
||||||
|
raisedHingeY: 0.056,
|
||||||
|
paperContactOffset: 0.0012
|
||||||
|
};
|
||||||
|
|
||||||
let readingProgress = readInitialProgress();
|
let readingProgress = readInitialProgress();
|
||||||
progressInput.value = readingProgress.toFixed(3);
|
progressInput.value = readingProgress.toFixed(3);
|
||||||
progressValue.value = readingProgress.toFixed(2);
|
progressValue.value = readingProgress.toFixed(2);
|
||||||
@@ -85,7 +92,7 @@ function rebuildBook() {
|
|||||||
clearGroup(book);
|
clearGroup(book);
|
||||||
|
|
||||||
const coverDepth = 2.34;
|
const coverDepth = 2.34;
|
||||||
const coverThickness = 0.022;
|
const coverThickness = BOOK_PROFILE.coverThickness;
|
||||||
const pageWidth = 1.62;
|
const pageWidth = 1.62;
|
||||||
const pageDepth = 2.24;
|
const pageDepth = 2.24;
|
||||||
const gutter = 0.12;
|
const gutter = 0.12;
|
||||||
@@ -94,13 +101,14 @@ function rebuildBook() {
|
|||||||
const leftThickness = THREE.MathUtils.lerp(sheetTick, fullBlock, readingProgress);
|
const leftThickness = THREE.MathUtils.lerp(sheetTick, fullBlock, readingProgress);
|
||||||
const rightThickness = THREE.MathUtils.lerp(fullBlock, sheetTick, readingProgress);
|
const rightThickness = THREE.MathUtils.lerp(fullBlock, sheetTick, readingProgress);
|
||||||
const foldX = clothFoldX(gutter);
|
const foldX = clothFoldX(gutter);
|
||||||
|
const spineWidth = fullBlock;
|
||||||
|
|
||||||
addCoverAssembly(pageWidth, coverDepth, coverThickness);
|
addCoverAssembly(pageWidth, coverDepth, coverThickness, spineWidth);
|
||||||
addSplinePageBlock(-1, pageWidth, pageDepth, leftThickness, foldX);
|
addSplinePageBlock(-1, pageWidth, pageDepth, leftThickness, foldX);
|
||||||
addSplinePageBlock(1, pageWidth, pageDepth, rightThickness, foldX);
|
addSplinePageBlock(1, pageWidth, pageDepth, rightThickness, foldX);
|
||||||
addPageLayerLines(-1, pageWidth, pageDepth, leftThickness, foldX);
|
addPageLayerLines(-1, pageWidth, pageDepth, leftThickness, foldX);
|
||||||
addPageLayerLines(1, pageWidth, pageDepth, rightThickness, foldX);
|
addPageLayerLines(1, pageWidth, pageDepth, rightThickness, foldX);
|
||||||
addClothSpine(pageDepth, gutter);
|
addClothSpine(pageDepth, spineWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearGroup(group) {
|
function clearGroup(group) {
|
||||||
@@ -113,24 +121,27 @@ function clearGroup(group) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCoverAssembly(pageWidth, depth, thickness) {
|
function addCoverAssembly(pageWidth, depth, thickness, spineWidth) {
|
||||||
const cover = new THREE.Mesh(createCoverAssemblyGeometry(pageWidth, depth, thickness), materials.cover);
|
const cover = new THREE.Mesh(createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth), materials.cover);
|
||||||
book.add(cover);
|
book.add(cover);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCoverAssemblyGeometry(pageWidth, depth, thickness) {
|
function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth) {
|
||||||
const overhang = 0.055;
|
const overhang = 0.055;
|
||||||
const hingeX = 0.18;
|
const spineHalf = spineWidth * 0.5;
|
||||||
const centerX = 0.095;
|
const hingeInset = 0.07;
|
||||||
const boardTopY = 0.023;
|
const outerX = pageWidth + overhang;
|
||||||
const centerTopY = -0.002;
|
const hingeX = spineHalf + hingeInset;
|
||||||
|
const outerTopY = BOOK_PROFILE.tableY + thickness;
|
||||||
|
const connectionTopY = BOOK_PROFILE.raisedHingeY;
|
||||||
|
const spineTopY = BOOK_PROFILE.tableY + thickness;
|
||||||
const section = [
|
const section = [
|
||||||
{ x: -pageWidth - overhang, y: boardTopY },
|
{ x: -outerX, y: outerTopY },
|
||||||
{ x: -hingeX, y: boardTopY },
|
{ x: -hingeX, y: connectionTopY },
|
||||||
{ x: -centerX, y: centerTopY },
|
{ x: -spineHalf, y: spineTopY },
|
||||||
{ x: centerX, y: centerTopY },
|
{ x: spineHalf, y: spineTopY },
|
||||||
{ x: hingeX, y: boardTopY },
|
{ x: hingeX, y: connectionTopY },
|
||||||
{ x: pageWidth + overhang, y: boardTopY }
|
{ x: outerX, y: outerTopY }
|
||||||
];
|
];
|
||||||
const positions = [];
|
const positions = [];
|
||||||
const uvs = [];
|
const uvs = [];
|
||||||
@@ -201,53 +212,24 @@ function clothFoldX(gutter) {
|
|||||||
return THREE.MathUtils.lerp(-spineTravel, spineTravel, readingProgress);
|
return THREE.MathUtils.lerp(-spineTravel, spineTravel, readingProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addClothSpine(depth, gutter) {
|
function addClothSpine(depth, spineWidth) {
|
||||||
const geometry = createClothSpineGeometry(depth, gutter);
|
const material = new THREE.LineBasicMaterial({ color: 0xb51f1f });
|
||||||
const spine = new THREE.Mesh(geometry, materials.spine);
|
const radiusX = spineWidth * 0.42;
|
||||||
book.add(spine);
|
const radiusY = 0.018;
|
||||||
}
|
const baseY = BOOK_PROFILE.tableY + BOOK_PROFILE.coverThickness + 0.002;
|
||||||
|
const profile = [];
|
||||||
function createClothSpineGeometry(depth, gutter) {
|
for (let i = 0; i <= 32; i += 1) {
|
||||||
const columns = 24;
|
const u = i / 32;
|
||||||
const rows = 18;
|
|
||||||
const positions = [];
|
|
||||||
const uvs = [];
|
|
||||||
const indices = [];
|
|
||||||
const radiusX = gutter * 0.52;
|
|
||||||
const radiusY = 0.04;
|
|
||||||
const baseY = -0.004;
|
|
||||||
|
|
||||||
for (let row = 0; row <= rows; row += 1) {
|
|
||||||
const v = row / rows;
|
|
||||||
const z = (v - 0.5) * depth * 0.94;
|
|
||||||
for (let column = 0; column <= columns; column += 1) {
|
|
||||||
const u = column / columns;
|
|
||||||
const theta = Math.PI * (1 - u);
|
const theta = Math.PI * (1 - u);
|
||||||
const x = Math.cos(theta) * radiusX;
|
const x = Math.cos(theta) * radiusX;
|
||||||
const y = baseY + Math.sin(theta) * radiusY + x * 0.12;
|
const y = baseY + Math.sin(theta) * radiusY;
|
||||||
const exposedY = Math.min(y, 0.038);
|
profile.push({ x, y });
|
||||||
const lengthSag = -0.006 * Math.sin(Math.PI * v);
|
|
||||||
positions.push(x, exposedY + lengthSag, z);
|
|
||||||
uvs.push(u, v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let row = 0; row < rows; row += 1) {
|
[depth * 0.5 + 0.008, -depth * 0.5 - 0.008].forEach((z) => {
|
||||||
for (let column = 0; column < columns; column += 1) {
|
const points = profile.map((point) => new THREE.Vector3(point.x, point.y, z));
|
||||||
const a = row * (columns + 1) + column;
|
book.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(points), material));
|
||||||
const b = a + 1;
|
});
|
||||||
const c = a + columns + 1;
|
|
||||||
const d = c + 1;
|
|
||||||
indices.push(a, c, b, b, c, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
geometry.setIndex(indices);
|
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
||||||
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
||||||
geometry.computeVertexNormals();
|
|
||||||
return geometry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pageBlockTopY(thickness, u, v) {
|
function pageBlockTopY(thickness, u, v) {
|
||||||
@@ -255,19 +237,21 @@ function pageBlockTopY(thickness, u, v) {
|
|||||||
const hinge = THREE.MathUtils.clamp(u / hingeWidth, 0, 1);
|
const hinge = THREE.MathUtils.clamp(u / hingeWidth, 0, 1);
|
||||||
const t = easeOutCubic(hinge);
|
const t = easeOutCubic(hinge);
|
||||||
const sewnY = 0.066;
|
const sewnY = 0.066;
|
||||||
const stackY = 0.032 + thickness;
|
const stackY = pageBlockBottomY(thickness, u, v) + thickness;
|
||||||
const flatCrown = 0.006 * Math.sin(Math.PI * v);
|
const flatCrown = 0.006 * Math.sin(Math.PI * v);
|
||||||
const foreCurl = 0.006 * smoothstep(THREE.MathUtils.clamp((u - 0.88) / 0.12, 0, 1));
|
const foreCurl = 0.006 * smoothstep(THREE.MathUtils.clamp((u - 0.88) / 0.12, 0, 1));
|
||||||
return sewnY * (1 - t) + (stackY + flatCrown + foreCurl) * t;
|
return sewnY * (1 - t) + (stackY + flatCrown + foreCurl) * t;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pageBlockBottomY(thickness, u, v) {
|
function pageBlockBottomY(thickness, u, v) {
|
||||||
const hingeWidth = 0.105;
|
const supportY = coverSupportTopY(u);
|
||||||
const hinge = THREE.MathUtils.clamp(u / hingeWidth, 0, 1);
|
return supportY + BOOK_PROFILE.paperContactOffset;
|
||||||
const t = easeOutCubic(hinge);
|
}
|
||||||
const sewnY = 0.026;
|
|
||||||
const stackY = 0.026;
|
function coverSupportTopY(u) {
|
||||||
return sewnY * (1 - t) + stackY * t;
|
const hingeSpan = 0.14;
|
||||||
|
const t = THREE.MathUtils.clamp((u - hingeSpan) / (1 - hingeSpan), 0, 1);
|
||||||
|
return THREE.MathUtils.lerp(BOOK_PROFILE.raisedHingeY, BOOK_PROFILE.coverThickness, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
function smoothstep(value) {
|
function smoothstep(value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user