🔬 These two lines convert a flat grid coordinate into a screen position with fake perspective. What happens if you remove the * perspective multiplier from xScreen (just use u * width) - does the depth illusion collapse?
const xScreen = width / 2 + u * width * perspective;
const yScreen = yBase - h * perspective * 0.4; // higher height -> visually higher
🔬 This loop draws the vertical ridge lines connecting each column front-to-back. What happens if you comment out this entire block - does the terrain still read as 3D without the column lines, using only the filled quads and row lines?
for (let c = 0; c < COLS; c++) {
beginShape();
for (let r = 0; r < ROWS; r++) {
const p = positions[r][c];
vertex(p.x, p.y);
}
endShape();
}
function draw() {
background(8, 8, 20); // dark night sky
// Time variable for smooth animation
const t = frameCount * 0.015;
// Mouse-driven controls (ref: https://p5js.org/reference/#/p5/map)
const mx = constrain(mouseX, 0, width);
const my = constrain(mouseY, 0, height);
// Primary spatial frequency: left = low, right = high
const primaryFreq = map(mx, 0, width, 0.6, 2.5);
// Amplitude: top = small waves, bottom = big mountains
const amplitude = map(my, 0, height, 40, 220);
// Precompute positions & heights for this frame
const positions = new Array(ROWS);
const heights = new Array(ROWS);
for (let r = 0; r < ROWS; r++) {
positions[r] = new Array(COLS);
heights[r] = new Array(COLS);
// Depth from front (0) to back (1)
const depth = r / (ROWS - 1);
// Perspective scaling: near rows appear wider, far rows narrower
const perspective = lerp(2.0, 0.6, depth);
// Base vertical position: near rows lower on screen, far rows higher
const yBase = lerp(height * 0.9, height * 0.2, depth);
for (let c = 0; c < COLS; c++) {
// Normalized coordinates in "world space"
const u = c / (COLS - 1) - 0.5; // -0.5 .. 0.5 horizontally
const v = depth; // 0 .. 1 in depth
// Height from overlapping sine waves
const h = computeHeight(u, v, t, primaryFreq, amplitude);
heights[r][c] = h;
// Perspective-mapped screen position
const xScreen = width / 2 + u * width * perspective;
const yScreen = yBase - h * perspective * 0.4; // higher height -> visually higher
positions[r][c] = { x: xScreen, y: yScreen, depth, perspective };
}
}
// Draw filled terrain mesh
noStroke(); // we'll add subtle grid lines afterwards
for (let r = 0; r < ROWS - 1; r++) {
for (let c = 0; c < COLS - 1; c++) {
const p00 = positions[r][c];
const p10 = positions[r][c + 1];
const p01 = positions[r + 1][c];
const p11 = positions[r + 1][c + 1];
const h00 = heights[r][c];
const h10 = heights[r][c + 1];
const h01 = heights[r + 1][c];
const h11 = heights[r + 1][c + 1];
const hAvg = (h00 + h10 + h01 + h11) / 4;
// Height-based color: valleys -> blue, peaks -> orange/red
const terrainColor = heightToColor(hAvg, amplitude);
fill(terrainColor);
// Quad for this grid cell (ref: https://p5js.org/reference/#/p5/beginShape)
beginShape();
vertex(p00.x, p00.y);
vertex(p10.x, p10.y);
vertex(p11.x, p11.y);
vertex(p01.x, p01.y);
endShape(CLOSE);
}
}
// Subtle grid lines connecting points
stroke(255, 255, 255, 45);
strokeWeight(1);
// Lines along rows (front-to-back bands)
for (let r = 0; r < ROWS; r++) {
beginShape();
for (let c = 0; c < COLS; c++) {
const p = positions[r][c];
vertex(p.x, p.y);
}
endShape();
}
// Lines along columns (left-to-right ridges)
for (let c = 0; c < COLS; c++) {
beginShape();
for (let r = 0; r < ROWS; r++) {
const p = positions[r][c];
vertex(p.x, p.y);
}
endShape();
}
// Optional: small cursor indicator to emphasize mouse control
drawMouseIndicator(primaryFreq, amplitude);
}