feat(about): widen and fade the river
This commit is contained in:
@@ -12,15 +12,10 @@ import {
|
||||
} from './scenePrimitives';
|
||||
import styles from './Scenes.module.css';
|
||||
|
||||
// Sharp 90-degree orthogonal isometric river path:
|
||||
// Enters from bottom-left along the isometric Z axis,
|
||||
// takes a sharp 90-degree orthogonal step across the isometric X axis at the center,
|
||||
// and turns 90 degrees to continue along the isometric Z axis towards the top-right.
|
||||
// Straight isometric river running between the two platform groups.
|
||||
const riverPath = [
|
||||
new THREE.Vector2(-1.3, -16),
|
||||
new THREE.Vector2(-1.3, 0),
|
||||
new THREE.Vector2(1.3, 0),
|
||||
new THREE.Vector2(1.3, 16),
|
||||
new THREE.Vector2(0, -20),
|
||||
new THREE.Vector2(0, 20)
|
||||
];
|
||||
|
||||
const createAboutScene: SceneFactory = ({ pointer, theme, reduceMotion }) => {
|
||||
@@ -111,19 +106,96 @@ const createAboutScene: SceneFactory = ({ pointer, theme, reduceMotion }) => {
|
||||
addPlate(secondaryPlates, x, z, width, depth, 0.5, theme === 'dark' ? 0x0b2f5f : 0xe6f0fe),
|
||||
);
|
||||
|
||||
const riverGeometry = new THREE.ShapeGeometry(createRibbonShape(riverPath, 1.8));
|
||||
const riverGeometry = new THREE.ShapeGeometry(createRibbonShape(riverPath, 4.5));
|
||||
const riverEndFade = 0.16;
|
||||
const fadeTextureSize = 256;
|
||||
const fadeTextureData = new Uint8Array(fadeTextureSize * 4);
|
||||
|
||||
for (let index = 0; index < fadeTextureSize; index += 1) {
|
||||
const progress = index / (fadeTextureSize - 1);
|
||||
const distanceFromEnd = Math.min(progress, 1 - progress);
|
||||
const opacity = Math.round(255 * Math.min(1, distanceFromEnd / riverEndFade));
|
||||
const offset = index * 4;
|
||||
|
||||
fadeTextureData[offset] = opacity;
|
||||
fadeTextureData[offset + 1] = opacity;
|
||||
fadeTextureData[offset + 2] = opacity;
|
||||
fadeTextureData[offset + 3] = 255;
|
||||
}
|
||||
|
||||
const riverFadeTexture = new THREE.DataTexture(
|
||||
fadeTextureData,
|
||||
1,
|
||||
fadeTextureSize,
|
||||
THREE.RGBAFormat,
|
||||
);
|
||||
riverFadeTexture.minFilter = THREE.LinearFilter;
|
||||
riverFadeTexture.magFilter = THREE.LinearFilter;
|
||||
riverFadeTexture.needsUpdate = true;
|
||||
|
||||
riverGeometry.computeBoundingBox();
|
||||
const riverBounds = riverGeometry.boundingBox;
|
||||
const riverMinY = riverBounds?.min.y ?? -16;
|
||||
const riverLength = Math.max((riverBounds?.max.y ?? 16) - riverMinY, 1);
|
||||
const riverPositions = riverGeometry.getAttribute('position');
|
||||
const riverUvs = riverGeometry.getAttribute('uv');
|
||||
|
||||
for (let index = 0; index < riverPositions.count; index += 1) {
|
||||
const pathProgress = (riverPositions.getY(index) - riverMinY) / riverLength;
|
||||
riverUvs.setXY(index, 0.5, pathProgress);
|
||||
}
|
||||
riverUvs.needsUpdate = true;
|
||||
|
||||
const waterMaterial = new THREE.MeshStandardMaterial({
|
||||
color: palette.water,
|
||||
emissive: palette.water,
|
||||
emissiveIntensity: theme === 'dark' ? 0.28 : 0.1,
|
||||
roughness: 0.52,
|
||||
alphaMap: riverFadeTexture,
|
||||
transparent: true,
|
||||
});
|
||||
const river = new THREE.Mesh(riverGeometry, waterMaterial);
|
||||
river.rotation.x = -Math.PI / 2;
|
||||
river.position.y = -0.23;
|
||||
world.add(river);
|
||||
|
||||
const riverEdge = new THREE.LineSegments(new THREE.EdgesGeometry(riverGeometry), edgeMaterial);
|
||||
const riverEdgeMaterial = new THREE.ShaderMaterial({
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
uniforms: {
|
||||
edgeColor: { value: new THREE.Color(palette.edge) },
|
||||
edgeOpacity: { value: theme === 'dark' ? 0.48 : 0.56 },
|
||||
fadeFraction: { value: riverEndFade },
|
||||
pathMinY: { value: riverMinY },
|
||||
pathLength: { value: riverLength },
|
||||
},
|
||||
vertexShader: `
|
||||
uniform float pathMinY;
|
||||
uniform float pathLength;
|
||||
varying float pathProgress;
|
||||
|
||||
void main() {
|
||||
pathProgress = clamp((position.y - pathMinY) / pathLength, 0.0, 1.0);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 edgeColor;
|
||||
uniform float edgeOpacity;
|
||||
uniform float fadeFraction;
|
||||
varying float pathProgress;
|
||||
|
||||
void main() {
|
||||
float startFade = smoothstep(0.0, fadeFraction, pathProgress);
|
||||
float endFade = smoothstep(0.0, fadeFraction, 1.0 - pathProgress);
|
||||
gl_FragColor = vec4(edgeColor, edgeOpacity * startFade * endFade);
|
||||
}
|
||||
`,
|
||||
});
|
||||
const riverEdge = new THREE.LineSegments(
|
||||
new THREE.EdgesGeometry(riverGeometry),
|
||||
riverEdgeMaterial,
|
||||
);
|
||||
riverEdge.rotation.copy(river.rotation);
|
||||
riverEdge.position.y = -0.21;
|
||||
world.add(riverEdge);
|
||||
@@ -133,7 +205,8 @@ const createAboutScene: SceneFactory = ({ pointer, theme, reduceMotion }) => {
|
||||
const direction = end.clone().sub(start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
for (let distance = 1.3; distance < length - 0.7; distance += 1.35) {
|
||||
const fadeDistance = length * riverEndFade;
|
||||
for (let distance = fadeDistance; distance < length - fadeDistance; distance += 1.35) {
|
||||
const at = start.clone().addScaledVector(direction, distance);
|
||||
const seam = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(1.72, 0.016),
|
||||
|
||||
Reference in New Issue
Block a user