Skip to content

Commit 9710747

Browse files
polish(web): paper-relative cursor UV + lower ambient on paper-rail
Two adjustments so the bend-layer work actually reads: - drop ambient light from 0.7 to 0.45 and bump key from 1.4 to 1.55, so the cursor bump, catenary sag, and edge curl have visible self-shading instead of being washed flat - convert mouseRawX/Y to paper-local UV via the camera frustum, so cursor proximity, hover lift, and edge curl track the paper's actual screen position. Previously the whole viewport mapped to UV, which mis-fired effects on the offset problem/how stages.
1 parent c86d1a5 commit 9710747

1 file changed

Lines changed: 53 additions & 12 deletions

File tree

web/components/canvas/paper-rail.tsx

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,10 @@ export function PaperRail() {
13461346
const scene = new THREE.Scene();
13471347
const camera = new THREE.PerspectiveCamera(28, W / H, 0.1, 100);
13481348
camera.position.set(0, 0, 8.6);
1349+
// Hoisted projection constant: tan(fov/2) lets us convert camera-Z
1350+
// into the world-space half-height of the visible frustum at any
1351+
// depth, used in the loop to map cursor NDC to paper-local UV.
1352+
const tanHalfFov = Math.tan((camera.fov * Math.PI) / 360);
13491353

13501354
const paperGroup = new THREE.Group();
13511355
scene.add(paperGroup);
@@ -1467,7 +1471,11 @@ export function PaperRail() {
14671471

14681472
const verlet = makeVerlet();
14691473

1470-
const keyLight = new THREE.DirectionalLight(0xfff8e8, 1.4);
1474+
// Lighting: ambient was 0.7, which flooded the scene and crushed
1475+
// the self-shading on the bend layer (cursor bump, catenary sag,
1476+
// edge curl all looked flat). Lower ambient + slightly higher key
1477+
// restores normal-driven contrast so the geometry actually reads.
1478+
const keyLight = new THREE.DirectionalLight(0xfff8e8, 1.55);
14711479
keyLight.position.set(-2, 2.5, 5);
14721480
scene.add(keyLight);
14731481
const fillLight = new THREE.DirectionalLight(0xffd9b8, 0.55);
@@ -1476,7 +1484,7 @@ export function PaperRail() {
14761484
const rimLight = new THREE.DirectionalLight(0xe8503a, 0.25);
14771485
rimLight.position.set(0, 0, -3);
14781486
scene.add(rimLight);
1479-
const ambient = new THREE.AmbientLight(0xffffff, 0.7);
1487+
const ambient = new THREE.AmbientLight(0xffffff, 0.45);
14801488
scene.add(ambient);
14811489

14821490
const DUST_COUNT = 280;
@@ -2012,21 +2020,54 @@ export function PaperRail() {
20122020
const waveTime = waveStart > 0 ? (now - waveStart) / 600 : 1;
20132021
const wave = waveTime < 1 ? waveTime : 0;
20142022

2015-
// Raw cursor UV in paper-screen space. Used as the spring target
2016-
// for the trailing bump position.
2017-
const cursorRawU = Math.max(0, Math.min(1, mouseRawX * 0.5 + 0.5));
2018-
const cursorRawV = Math.max(0, Math.min(1, -mouseRawY * 0.5 + 0.5));
2023+
// Paper-relative cursor UV. Convert paper world-space centre and
2024+
// half-extents into NDC using the camera's frustum, then map the
2025+
// cursor's NDC offset from the paper centre into 0..1 UV space.
2026+
// Pre-existing code mapped the *whole viewport* to UV, which fired
2027+
// edge effects on offset stages (problem/how) where the paper
2028+
// doesn't fill the centre.
2029+
const halfHWorld = spCamZ.value * tanHalfFov;
2030+
const halfWWorld = halfHWorld * camera.aspect;
2031+
const paperRadiusNDCx = (PAPER_W * 0.5 * stageScale) / halfWWorld;
2032+
const paperRadiusNDCy = (PAPER_H * 0.5 * stageScale) / halfHWorld;
2033+
const paperCentreNDCx = spX.value / halfWWorld;
2034+
const paperCentreNDCy = spY.value / halfHWorld;
2035+
const cursorOffNDCx = mouseRawX - paperCentreNDCx;
2036+
const cursorOffNDCy = mouseRawY - paperCentreNDCy;
2037+
const cursorRawU = Math.max(
2038+
0,
2039+
Math.min(1, cursorOffNDCx / (2 * paperRadiusNDCx) + 0.5),
2040+
);
2041+
const cursorRawV = Math.max(
2042+
0,
2043+
Math.min(1, -cursorOffNDCy / (2 * paperRadiusNDCy) + 0.5),
2044+
);
20192045
// Cursor velocity in NDC. mouseX/Y are the lowpass-filtered cursor
20202046
// (pre-existing); the diff vs raw gives an instantaneous velocity
20212047
// estimate without needing per-frame state.
20222048
const cursorVelX = mouseRawX - mouseX;
20232049
const cursorVelY = mouseRawY - mouseY;
2024-
// Hover lift: when the cursor is roughly over the paper's
2025-
// viewport region, add a tiny persistent positive bias. Kept
2026-
// small (0.004) so it reads as the paper noticing the cursor,
2027-
// not lifting toward it.
2028-
const overU = Math.max(0, 1 - Math.max(0, Math.abs(mouseRawX) - 0.35) * 5);
2029-
const overV = Math.max(0, 1 - Math.max(0, Math.abs(mouseRawY) - 0.5) * 5);
2050+
// Hover gate: 1 inside the paper's actual NDC bounds, falling to
2051+
// 0 over a 50%-of-half-extent feathered band past each edge. Now
2052+
// tracks the paper's real screen position regardless of stage.
2053+
const fadeU = paperRadiusNDCx * 0.5;
2054+
const fadeV = paperRadiusNDCy * 0.5;
2055+
const overU = Math.max(
2056+
0,
2057+
Math.min(
2058+
1,
2059+
1
2060+
- Math.max(0, Math.abs(cursorOffNDCx) - paperRadiusNDCx) / fadeU,
2061+
),
2062+
);
2063+
const overV = Math.max(
2064+
0,
2065+
Math.min(
2066+
1,
2067+
1
2068+
- Math.max(0, Math.abs(cursorOffNDCy) - paperRadiusNDCy) / fadeV,
2069+
),
2070+
);
20302071
const hoverLift = overU * overV * 0.004;
20312072
// Press input: signed by horizontal motion (so swipes have
20322073
// direction), magnitude boosted by total speed (so vertical

0 commit comments

Comments
 (0)