import * as THREE from 'three'; import type { PierModel, Vec2 } from '../core/geometry'; import type { TerrainGrid } from '../core/ground-model'; import { buildPierScene, type PierScene } from './scene'; import { terrainGeometry } from './terrain'; /** * Scene offset of a pier's group inside the shared frame of the reference pier. Each pier's scene is centred on its own * pivot, so moving it by the pivot difference puts it at its national-grid place (x = E, z = −N, as in stretchMatrix). */ export function pierOffset(pivot: Vec2, reference: Vec2): { x: number; z: number } { return { x: pivot.e - reference.e, z: reference.n - pivot.n }; } export interface AllPiers { /** Holds every pier's group, placed by pierOffset. Used for the camera fit and picking. */ piers: THREE.Group; /** Shared terrain grid in the reference frame, or null without a terrain model. */ terrain: THREE.Mesh | null; /** Scene of each pier, keyed by the pier name. Parts are shown per pier. */ scenes: Map; /** Scene with the same shape the single-pier view expects (root = pier group, size = whole span). */ view: PierScene; } /** * Builds the concrete members of several piers in one frame (전체 보기, 콘크리트만: no rebar). The first entry is the * reference. With a terrain grid it is drawn once and the per-pier ground lines are hidden. */ export function buildAllPiers(entries: { name: string; model: PierModel; groundEl?: number | null }[], terrain?: TerrainGrid): AllPiers { const piers = new THREE.Group(); const scenes = new Map(); const reference = entries[0].model.pivot; const terrainSurface = terrain ? terrainGeometry(terrain, reference) : undefined; const hasTerrainSurface = Boolean(terrainSurface && (terrainSurface.getIndex()?.count ?? 0) > 0); for (const { name, model, groundEl } of entries) { const scene = buildPierScene(model, undefined, groundEl); // Without terrain each pier keeps its own 지반고 line; with terrain the shared grid replaces them. scene.parts.ground.visible = !hasTerrainSurface; const { x, z } = pierOffset(model.pivot, reference); scene.root.position.set(x, 0, z); scene.root.userData.pier = name; piers.add(scene.root); scenes.set(name, scene); } let terrainMesh: THREE.Mesh | null = null; if (terrainSurface && hasTerrainSurface) { const material = new THREE.MeshStandardMaterial({ color: 0xb8c7b0, roughness: 0.9, metalness: 0, transparent: true, opacity: 0.35, depthWrite: false, side: THREE.DoubleSide }); terrainMesh = new THREE.Mesh(terrainSurface, material); } const box = new THREE.Box3().setFromObject(piers); const size = box.getSize(new THREE.Vector3()).length(); const view: PierScene = { root: piers, parts: scenes.get(entries[0].name)!.parts, size }; return { piers, terrain: terrainMesh, scenes, view }; }