Round WebGL book cover edges

This commit is contained in:
2026-06-06 02:48:57 +02:00
parent 874d360d22
commit f634500121
+116 -94
View File
@@ -147,19 +147,29 @@ function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth, co
const uvs = []; const uvs = [];
const indices = []; const indices = [];
const groups = []; const groups = [];
const vertexCache = new Map();
const halfDepth = depth * 0.5; const halfDepth = depth * 0.5;
const edgeRadius = Math.min(depth * 0.015, thickness * 0.45, 0.035); const edgeRadius = Math.min(thickness * 0.5, PROCEDURAL_BOOK.COVER_OVERHANG * 0.5);
const edgeProfile = [ const cornerSteps = 8;
{ inset: edgeRadius, drop: 0 }, const sideSteps = 18;
{ inset: edgeRadius * 0.48, drop: edgeRadius * 0.16 }, const edgeSteps = 18;
{ inset: edgeRadius * 0.12, drop: edgeRadius * 0.42 }, const leftX = section[0].x;
{ inset: 0, drop: edgeRadius * 0.72 }, const rightX = section[section.length - 1].x;
];
const push = (point) => { const push = (point) => {
const key = [
point.x.toFixed(5),
point.y.toFixed(5),
point.z.toFixed(5),
point.u.toFixed(5),
point.v.toFixed(5)
].join('|');
const cached = vertexCache.get(key);
if (cached !== undefined) return cached;
const index = positions.length / 3; const index = positions.length / 3;
positions.push(point.x, point.y, point.z); positions.push(point.x, point.y, point.z);
uvs.push(point.u, point.v); uvs.push(point.u, point.v);
vertexCache.set(key, index);
return index; return index;
}; };
@@ -169,106 +179,118 @@ function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth, co
groups.push({ start, count: quadIndices.length, materialIndex }); groups.push({ start, count: quadIndices.length, materialIndex });
}; };
const pointAt = (source, y, z, u, v) => ({ x: source.x, y, z, u, v });
const edgePoint = (source, side, profilePoint, u, bottom = false) => {
const topY = source.y;
const bottomY = source.y - thickness;
return pointAt(
source,
bottom ? bottomY + profilePoint.drop : topY - profilePoint.drop,
side * (halfDepth - profilePoint.inset),
u,
side > 0 ? 1 : 0
);
};
const addQuad = (materialIndex, a, b, c, d) => { const addQuad = (materialIndex, a, b, c, d) => {
const base = positions.length / 3; const aIndex = push(a);
push(a); const bIndex = push(b);
push(b); const cIndex = push(c);
push(c); const dIndex = push(d);
push(d); pushGroup(materialIndex, [aIndex, bIndex, cIndex, cIndex, bIndex, dIndex]);
pushGroup(materialIndex, [base, base + 1, base + 2, base + 2, base + 1, base + 3]);
}; };
for (let i = 0; i < section.length - 1; i += 1) { const coverYAtX = (x) => {
const left = section[i]; if (x <= leftX) return section[0].y;
const right = section[i + 1]; for (let index = 0; index < section.length - 1; index += 1) {
const leftU = i / (section.length - 1); const from = section[index];
const rightU = (i + 1) / (section.length - 1); const to = section[index + 1];
const topMaterial = coverSegmentMaterialIndex(i); if (x <= to.x) {
const innerFront = halfDepth - edgeRadius; const t = (x - from.x) / (to.x - from.x || 1);
const innerBack = -halfDepth + edgeRadius; return THREE.MathUtils.lerp(from.y, to.y, t);
addQuad(
topMaterial,
pointAt(left, left.y, innerFront, leftU, 1),
pointAt(right, right.y, innerFront, rightU, 1),
pointAt(left, left.y, innerBack, leftU, 0),
pointAt(right, right.y, innerBack, rightU, 0)
);
addQuad(
3,
pointAt(left, left.y - thickness, innerBack, leftU, 0),
pointAt(right, right.y - thickness, innerBack, rightU, 0),
pointAt(left, left.y - thickness, innerFront, leftU, 1),
pointAt(right, right.y - thickness, innerFront, rightU, 1)
);
[-1, 1].forEach((side) => {
for (let edgeIndex = 0; edgeIndex < edgeProfile.length - 1; edgeIndex += 1) {
const current = edgeProfile[edgeIndex];
const next = edgeProfile[edgeIndex + 1];
addQuad(
3,
edgePoint(left, side, current, leftU, false),
edgePoint(right, side, current, rightU, false),
edgePoint(left, side, next, leftU, false),
edgePoint(right, side, next, rightU, false)
);
addQuad(
3,
edgePoint(left, side, next, leftU, true),
edgePoint(right, side, next, rightU, true),
edgePoint(left, side, current, leftU, true),
edgePoint(right, side, current, rightU, true)
);
} }
}
const sideEdge = edgeProfile[edgeProfile.length - 1]; return section[section.length - 1].y;
addQuad( };
3, const materialAtX = (x) => {
edgePoint(left, side, sideEdge, leftU, false), for (let index = 0; index < section.length - 1; index += 1) {
edgePoint(right, side, sideEdge, rightU, false), if (x <= section[index + 1].x) return coverSegmentMaterialIndex(index);
edgePoint(left, side, sideEdge, leftU, true), }
edgePoint(right, side, sideEdge, rightU, true) return coverSegmentMaterialIndex(section.length - 2);
); };
const uAt = (x) => (x - leftX) / (rightX - leftX || 1);
const vAt = (z) => (z + halfDepth) / depth;
const pointAt = (x, y, z) => ({ x, y, z, u: uAt(x), v: vAt(z) });
const edgeProfile = Array.from({ length: edgeSteps + 1 }, (_, index) => {
const angle = Math.PI * 0.5 - (index / edgeSteps) * Math.PI;
return {
inset: edgeRadius - Math.cos(angle) * edgeRadius,
yOffset: -edgeRadius + Math.sin(angle) * edgeRadius
};
});
const roundedRectContour = (inset) => {
const hx = rightX - inset;
const hz = halfDepth - inset;
const cornerRadius = Math.max(0.0001, edgeRadius - inset);
const points = [];
const pushLinear = (fromX, fromZ, toX, toZ, steps) => {
for (let step = 0; step <= steps; step += 1) {
if (points.length && step === 0) continue;
const t = step / steps;
points.push({
x: THREE.MathUtils.lerp(fromX, toX, t),
z: THREE.MathUtils.lerp(fromZ, toZ, t)
}); });
} }
};
const pushCorner = (centerX, centerZ, fromAngle, toAngle) => {
for (let step = 1; step <= cornerSteps; step += 1) {
const t = step / cornerSteps;
const angle = THREE.MathUtils.lerp(fromAngle, toAngle, t);
points.push({
x: centerX + Math.cos(angle) * cornerRadius,
z: centerZ + Math.sin(angle) * cornerRadius
});
}
};
[0, section.length - 1].forEach((pointIndex) => { pushLinear(-hx + cornerRadius, hz, hx - cornerRadius, hz, sideSteps);
const point = section[pointIndex]; pushCorner(hx - cornerRadius, hz - cornerRadius, Math.PI * 0.5, 0);
const u = pointIndex / (section.length - 1); pushLinear(hx, hz - cornerRadius, hx, -hz + cornerRadius, sideSteps);
if (pointIndex === 0) { pushCorner(hx - cornerRadius, -hz + cornerRadius, 0, -Math.PI * 0.5);
pushLinear(hx - cornerRadius, -hz, -hx + cornerRadius, -hz, sideSteps);
pushCorner(-hx + cornerRadius, -hz + cornerRadius, -Math.PI * 0.5, -Math.PI);
pushLinear(-hx, -hz + cornerRadius, -hx, hz - cornerRadius, sideSteps);
pushCorner(-hx + cornerRadius, hz - cornerRadius, Math.PI, Math.PI * 0.5);
return points;
};
const profileContours = edgeProfile.map((profile) => roundedRectContour(profile.inset).map((point) => {
const topY = coverYAtX(point.x);
return pointAt(point.x, topY + profile.yOffset, point.z);
}));
const topXs = [
leftX + edgeRadius,
...section.slice(1, -1).map((point) => point.x),
rightX - edgeRadius
].sort((a, b) => a - b);
const topZs = [-halfDepth + edgeRadius, halfDepth - edgeRadius];
for (let index = 0; index < topXs.length - 1; index += 1) {
const left = topXs[index];
const right = topXs[index + 1];
const materialIndex = materialAtX((left + right) * 0.5);
addQuad( addQuad(
3, materialIndex,
pointAt(point, point.y, halfDepth, u, 1), pointAt(left, coverYAtX(left), topZs[1]),
pointAt(point, point.y, -halfDepth, u, 0), pointAt(right, coverYAtX(right), topZs[1]),
pointAt(point, point.y - thickness, halfDepth, u, 1), pointAt(left, coverYAtX(left), topZs[0]),
pointAt(point, point.y - thickness, -halfDepth, u, 0) pointAt(right, coverYAtX(right), topZs[0])
); );
} else {
addQuad( addQuad(
3, 3,
pointAt(point, point.y, halfDepth, u, 1), pointAt(left, coverYAtX(left) - thickness, topZs[0]),
pointAt(point, point.y - thickness, halfDepth, u, 1), pointAt(right, coverYAtX(right) - thickness, topZs[0]),
pointAt(point, point.y, -halfDepth, u, 0), pointAt(left, coverYAtX(left) - thickness, topZs[1]),
pointAt(point, point.y - thickness, -halfDepth, u, 0) pointAt(right, coverYAtX(right) - thickness, topZs[1])
); );
} }
});
for (let profileIndex = 0; profileIndex < profileContours.length - 1; profileIndex += 1) {
const current = profileContours[profileIndex];
const next = profileContours[profileIndex + 1];
for (let pointIndex = 0; pointIndex < current.length; pointIndex += 1) {
const nextPointIndex = (pointIndex + 1) % current.length;
addQuad(3, current[pointIndex], current[nextPointIndex], next[pointIndex], next[nextPointIndex]);
}
}
const geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
geometry.setIndex(indices); geometry.setIndex(indices);