feat(ground): draw the terrain grid around the piers in the 3D view
- ground-model.ts: read schema 2 terrain_grid (origin, step, nx, ny, z), checked the same way as the stored project form; schema 1 is still read. - view/terrain.ts: the grid as a mesh in pier-scene coordinates (x = E − pivot E, z = −(N − pivot N), y = EL), same as the pier frame. Cells with a missing corner are not drawn. - scene.ts: draws the terrain in place of the 60 m plane when the model has a grid; the camera fits the members, not the terrain. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be76425c8f
commit
bfd41d7c8d
6 files changed
+200
-16
No files matched your search
+20
-7
@@ -1,6 +1,8 @@
|
||||
import * as THREE from 'three';
|
||||
import type { PierModel } from '../core/geometry';
|
||||
import { solplateGeometry } from '../core/solplate';
|
||||
import type { TerrainGrid } from '../core/ground-model';
|
||||
import { terrainGeometry } from './terrain';
|
||||
|
||||
export type PartName = 'coping' | 'columns' | 'footings' | 'piles' | 'blinding' | 'blocks' | 'pads' | 'bearings' | 'solplates' | 'ground';
|
||||
|
||||
@@ -73,7 +75,7 @@ export interface PierScene {
|
||||
size: number;
|
||||
}
|
||||
|
||||
export function buildPierScene(m: PierModel): PierScene {
|
||||
export function buildPierScene(m: PierModel, terrain?: TerrainGrid): PierScene {
|
||||
const Ms = m.stretches.map((_, k) => stretchMatrix(m, k));
|
||||
const parts = {} as Record<PartName, THREE.Group>;
|
||||
for (const key of Object.keys(PART_LABELS) as PartName[]) {
|
||||
@@ -153,15 +155,26 @@ export function buildPierScene(m: PierModel): PierScene {
|
||||
}
|
||||
}
|
||||
|
||||
// Ground: a translucent plane at the ground EL, used only to judge soil cover (not a computed input).
|
||||
const ground = new THREE.PlaneGeometry(60, 60);
|
||||
ground.rotateX(-Math.PI / 2);
|
||||
ground.translate(m.copingL / 2, m.groundEl, 0);
|
||||
add('ground', ground, 0.35);
|
||||
// Ground: the terrain grid around the piers when the ground model has one (수 m 떨어진 지형까지 보임),
|
||||
// otherwise a translucent plane at the ground EL, used only to judge soil cover (not a computed input).
|
||||
if (terrain) {
|
||||
const material = new THREE.MeshStandardMaterial({ color: COLORS.ground, roughness: 0.9, metalness: 0, transparent: true, opacity: 0.35, side: THREE.DoubleSide });
|
||||
material.userData.baseOpacity = 0.35;
|
||||
parts.ground.add(new THREE.Mesh(terrainGeometry(terrain, m.pivot), material));
|
||||
} else {
|
||||
const ground = new THREE.PlaneGeometry(60, 60);
|
||||
ground.rotateX(-Math.PI / 2);
|
||||
ground.translate(m.copingL / 2, m.groundEl, 0);
|
||||
add('ground', ground, 0.35);
|
||||
}
|
||||
|
||||
const root = new THREE.Group();
|
||||
for (const key of Object.keys(parts) as PartName[]) root.add(parts[key]);
|
||||
const box = new THREE.Box3().setFromObject(root);
|
||||
// The camera fits the pier, not the terrain: with a terrain grid the size comes from the members only.
|
||||
const box = new THREE.Box3();
|
||||
for (const key of Object.keys(parts) as PartName[]) {
|
||||
if (key !== 'ground' || !terrain) box.expandByObject(parts[key]);
|
||||
}
|
||||
const size = Math.max(...box.getSize(new THREE.Vector3()).toArray());
|
||||
return { root, parts, size };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user