Round WebGL book cover edges
This commit is contained in:
@@ -147,19 +147,29 @@ function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth, co
|
||||
const uvs = [];
|
||||
const indices = [];
|
||||
const groups = [];
|
||||
const vertexCache = new Map();
|
||||
const halfDepth = depth * 0.5;
|
||||
const edgeRadius = Math.min(depth * 0.015, thickness * 0.45, 0.035);
|
||||
const edgeProfile = [
|
||||
{ inset: edgeRadius, drop: 0 },
|
||||
{ inset: edgeRadius * 0.48, drop: edgeRadius * 0.16 },
|
||||
{ inset: edgeRadius * 0.12, drop: edgeRadius * 0.42 },
|
||||
{ inset: 0, drop: edgeRadius * 0.72 },
|
||||
];
|
||||
const edgeRadius = Math.min(thickness * 0.5, PROCEDURAL_BOOK.COVER_OVERHANG * 0.5);
|
||||
const cornerSteps = 8;
|
||||
const sideSteps = 18;
|
||||
const edgeSteps = 18;
|
||||
const leftX = section[0].x;
|
||||
const rightX = section[section.length - 1].x;
|
||||
|
||||
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;
|
||||
positions.push(point.x, point.y, point.z);
|
||||
uvs.push(point.u, point.v);
|
||||
vertexCache.set(key, index);
|
||||
return index;
|
||||
};
|
||||
|
||||
@@ -169,106 +179,118 @@ function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth, co
|
||||
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 base = positions.length / 3;
|
||||
push(a);
|
||||
push(b);
|
||||
push(c);
|
||||
push(d);
|
||||
pushGroup(materialIndex, [base, base + 1, base + 2, base + 2, base + 1, base + 3]);
|
||||
const aIndex = push(a);
|
||||
const bIndex = push(b);
|
||||
const cIndex = push(c);
|
||||
const dIndex = push(d);
|
||||
pushGroup(materialIndex, [aIndex, bIndex, cIndex, cIndex, bIndex, dIndex]);
|
||||
};
|
||||
|
||||
for (let i = 0; i < section.length - 1; i += 1) {
|
||||
const left = section[i];
|
||||
const right = section[i + 1];
|
||||
const leftU = i / (section.length - 1);
|
||||
const rightU = (i + 1) / (section.length - 1);
|
||||
const topMaterial = coverSegmentMaterialIndex(i);
|
||||
const innerFront = halfDepth - edgeRadius;
|
||||
const innerBack = -halfDepth + edgeRadius;
|
||||
const coverYAtX = (x) => {
|
||||
if (x <= leftX) return section[0].y;
|
||||
for (let index = 0; index < section.length - 1; index += 1) {
|
||||
const from = section[index];
|
||||
const to = section[index + 1];
|
||||
if (x <= to.x) {
|
||||
const t = (x - from.x) / (to.x - from.x || 1);
|
||||
return THREE.MathUtils.lerp(from.y, to.y, t);
|
||||
}
|
||||
}
|
||||
return section[section.length - 1].y;
|
||||
};
|
||||
const materialAtX = (x) => {
|
||||
for (let index = 0; index < section.length - 1; index += 1) {
|
||||
if (x <= section[index + 1].x) return coverSegmentMaterialIndex(index);
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
pushLinear(-hx + cornerRadius, hz, hx - cornerRadius, hz, sideSteps);
|
||||
pushCorner(hx - cornerRadius, hz - cornerRadius, Math.PI * 0.5, 0);
|
||||
pushLinear(hx, hz - cornerRadius, hx, -hz + cornerRadius, sideSteps);
|
||||
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(
|
||||
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)
|
||||
materialIndex,
|
||||
pointAt(left, coverYAtX(left), topZs[1]),
|
||||
pointAt(right, coverYAtX(right), topZs[1]),
|
||||
pointAt(left, coverYAtX(left), topZs[0]),
|
||||
pointAt(right, coverYAtX(right), topZs[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)
|
||||
pointAt(left, coverYAtX(left) - thickness, topZs[0]),
|
||||
pointAt(right, coverYAtX(right) - thickness, topZs[0]),
|
||||
pointAt(left, coverYAtX(left) - thickness, topZs[1]),
|
||||
pointAt(right, coverYAtX(right) - thickness, topZs[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];
|
||||
addQuad(
|
||||
3,
|
||||
edgePoint(left, side, sideEdge, leftU, false),
|
||||
edgePoint(right, side, sideEdge, rightU, false),
|
||||
edgePoint(left, side, sideEdge, leftU, true),
|
||||
edgePoint(right, side, sideEdge, rightU, true)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
[0, section.length - 1].forEach((pointIndex) => {
|
||||
const point = section[pointIndex];
|
||||
const u = pointIndex / (section.length - 1);
|
||||
if (pointIndex === 0) {
|
||||
addQuad(
|
||||
3,
|
||||
pointAt(point, point.y, halfDepth, u, 1),
|
||||
pointAt(point, point.y, -halfDepth, u, 0),
|
||||
pointAt(point, point.y - thickness, halfDepth, u, 1),
|
||||
pointAt(point, point.y - thickness, -halfDepth, u, 0)
|
||||
);
|
||||
} else {
|
||||
addQuad(
|
||||
3,
|
||||
pointAt(point, point.y, halfDepth, u, 1),
|
||||
pointAt(point, point.y - thickness, halfDepth, u, 1),
|
||||
pointAt(point, point.y, -halfDepth, u, 0),
|
||||
pointAt(point, point.y - thickness, -halfDepth, u, 0)
|
||||
);
|
||||
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();
|
||||
geometry.setIndex(indices);
|
||||
|
||||
Reference in New Issue
Block a user