feat(about): implement 90-degree orthogonal isometric river with large-screen overflow and responsive timeline
This commit is contained in:
@@ -60,19 +60,52 @@ export function fitOrthographicCamera(
|
||||
|
||||
export function createRibbonShape(points: THREE.Vector2[], width: number) {
|
||||
const halfWidth = width / 2;
|
||||
const offsetPoint = (index: number, direction: 1 | -1) => {
|
||||
const current = points[index].clone();
|
||||
const previous = points[Math.max(0, index - 1)];
|
||||
const next = points[Math.min(points.length - 1, index + 1)];
|
||||
const tangent = next.clone().sub(previous).normalize();
|
||||
return current.add(new THREE.Vector2(-tangent.y, tangent.x).multiplyScalar(halfWidth * direction));
|
||||
};
|
||||
const n = points.length;
|
||||
if (n < 2) return new THREE.Shape();
|
||||
|
||||
const left: THREE.Vector2[] = [];
|
||||
const right: THREE.Vector2[] = [];
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
let normal: THREE.Vector2;
|
||||
let miterScale = 1;
|
||||
|
||||
if (i === 0) {
|
||||
const d = points[1].clone().sub(points[0]).normalize();
|
||||
normal = new THREE.Vector2(-d.y, d.x);
|
||||
} else if (i === n - 1) {
|
||||
const d = points[n - 1].clone().sub(points[n - 2]).normalize();
|
||||
normal = new THREE.Vector2(-d.y, d.x);
|
||||
} else {
|
||||
const d1 = points[i].clone().sub(points[i - 1]).normalize();
|
||||
const d2 = points[i + 1].clone().sub(points[i]).normalize();
|
||||
const n1 = new THREE.Vector2(-d1.y, d1.x);
|
||||
const n2 = new THREE.Vector2(-d2.y, d2.x);
|
||||
const miter = n1.clone().add(n2);
|
||||
const len = miter.length();
|
||||
if (len > 0.0001) {
|
||||
miter.normalize();
|
||||
const dot = Math.max(miter.dot(n1), 0.25);
|
||||
miterScale = Math.min(1 / dot, 2.5);
|
||||
normal = miter;
|
||||
} else {
|
||||
normal = n1;
|
||||
}
|
||||
}
|
||||
|
||||
const offset = normal.clone().multiplyScalar(halfWidth * miterScale);
|
||||
left.push(points[i].clone().add(offset));
|
||||
right.push(points[i].clone().sub(offset));
|
||||
}
|
||||
|
||||
const left = points.map((_, index) => offsetPoint(index, 1));
|
||||
const right = points.map((_, index) => offsetPoint(index, -1)).reverse();
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(left[0].x, left[0].y);
|
||||
[...left.slice(1), ...right].forEach((point) => shape.lineTo(point.x, point.y));
|
||||
for (let i = 1; i < left.length; i++) {
|
||||
shape.lineTo(left[i].x, left[i].y);
|
||||
}
|
||||
for (let i = right.length - 1; i >= 0; i--) {
|
||||
shape.lineTo(right[i].x, right[i].y);
|
||||
}
|
||||
shape.closePath();
|
||||
return shape;
|
||||
}
|
||||
@@ -92,7 +125,7 @@ export function createMesa(
|
||||
halfWidth - inset, height, halfDepth - inset, -halfWidth + inset, height, halfDepth - inset,
|
||||
];
|
||||
const indices = [
|
||||
4, 5, 6, 4, 6, 7,
|
||||
4, 6, 5, 4, 7, 6,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
@@ -101,15 +134,33 @@ export function createMesa(
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||
geometry.setIndex(indices);
|
||||
geometry.clearGroups();
|
||||
geometry.addGroup(0, 6, 0);
|
||||
geometry.addGroup(6, 6, 1);
|
||||
geometry.addGroup(12, 6, 2);
|
||||
geometry.addGroup(18, 6, 1);
|
||||
geometry.addGroup(24, 6, 2);
|
||||
geometry.computeVertexNormals();
|
||||
|
||||
const mesa = new THREE.Mesh(
|
||||
geometry,
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: palette.mountain,
|
||||
flatShading: true,
|
||||
roughness: 0.9,
|
||||
}),
|
||||
[
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: palette.mountainLight,
|
||||
flatShading: true,
|
||||
roughness: 0.9,
|
||||
}),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: palette.mountain,
|
||||
flatShading: true,
|
||||
roughness: 0.92,
|
||||
}),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: palette.mountainDark,
|
||||
flatShading: true,
|
||||
roughness: 0.94,
|
||||
}),
|
||||
],
|
||||
);
|
||||
mesa.castShadow = true;
|
||||
mesa.receiveShadow = true;
|
||||
|
||||
Reference in New Issue
Block a user