import * as THREE from 'three'; import type { TerrainGrid } from '../core/ground-model'; /** * Terrain grid → mesh in scene coordinates. The scene origin is the pier centre (pivot), with y up: * x = X − pivot.e, y = EL, z = −(Y − pivot.n). This is the straight-frame matrix of the pier scene (stretchMatrix, k = 0), * so the terrain lines up with the footings and copings. A cell is drawn only when its four corners have data. */ export function terrainGeometry(grid: TerrainGrid, pivot: { e: number; n: number }): THREE.BufferGeometry { const { origin, step, nx, ny, z } = grid; const positions: number[] = []; const index: number[] = []; const vertexOf = new Int32Array(nx * ny).fill(-1); for (let j = 0; j < ny; j++) { for (let i = 0; i < nx; i++) { const h = z[j * nx + i]; if (h === null) continue; vertexOf[j * nx + i] = positions.length / 3; positions.push(origin[0] + i * step - pivot.e, h, -(origin[1] + j * step - pivot.n)); } } for (let j = 0; j < ny - 1; j++) { for (let i = 0; i < nx - 1; i++) { const a = vertexOf[j * nx + i]; const b = vertexOf[j * nx + i + 1]; const c = vertexOf[(j + 1) * nx + i]; const d = vertexOf[(j + 1) * nx + i + 1]; if (a < 0 || b < 0 || c < 0 || d < 0) continue; index.push(a, c, b, b, c, d); } } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); geometry.setIndex(index); geometry.computeVertexNormals(); return geometry; }