import * as THREE from "three"; import { PATH_POINTS, PATHS, ROAD_HALF_WIDTH, GRID_CELL_SIZE, } from "../config/gameConfig.js"; export class PathBuilder { constructor(scene) { this.scene = scene; // Prepare one or many paths, snapping points to grid const snap = (v) => { const out = v.clone ? v.clone() : new THREE.Vector3(v.x, v.y ?? 0, v.z); out.x = Math.round(out.x / GRID_CELL_SIZE) * GRID_CELL_SIZE; out.z = Math.round(out.z / GRID_CELL_SIZE) * GRID_CELL_SIZE; out.y = 0; return out; }; const hasBranches = Array.isArray(PATHS) && PATHS.length > 0; this.paths = hasBranches ? PATHS.map((path) => path.map(snap)) : [PATH_POINTS.map(snap)]; this.roadMeshes = []; // Materials this.roadMat = new THREE.MeshStandardMaterial({ color: 0x393c41, metalness: 0.1, roughness: 0.9, }); } buildPath() { // Visualize path lines this.createPathLines(); // Deduplicate segments across paths const segKey = (a, b) => { // direction-agnostic key const ax = a.x.toFixed(4); const az = a.z.toFixed(4); const bx = b.x.toFixed(4); const bz = b.z.toFixed(4); return ax < bx || (ax === bx && az <= bz) ? `${ax},${az}|${bx},${bz}` : `${bx},${bz}|${ax},${az}`; }; const seenSegs = new Set(); for (const pts of this.paths) { for (let i = 0; i < pts.length - 1; i++) { const a = pts[i]; const b = pts[i + 1]; const key = segKey(a, b); if (!seenSegs.has(key)) { seenSegs.add(key); this.addSegment(a, b); } } } // Corner tiles at turns; deduplicate by center const seenCorners = new Set(); for (const pts of this.paths) { for (let i = 1; i < pts.length - 1; i++) { const prev = pts[i - 1]; const cur = pts[i]; const next = pts[i + 1]; const v1 = new THREE.Vector3().subVectors(cur, prev); const v2 = new THREE.Vector3().subVectors(next, cur); v1.y = 0; v2.y = 0; const isV1H = Math.abs(v1.x) > Math.abs(v1.z); const isV2H = Math.abs(v2.x) > Math.abs(v2.z); const isTurn = (isV1H && !isV2H) || (!isV1H && isV2H); if (isTurn) { const key = `${cur.x.toFixed(4)},${cur.z.toFixed(4)}`; if (!seenCorners.has(key)) { seenCorners.add(key); this.addCornerTile(cur); } } } } } createPathLines() { const pathLineMat = new THREE.LineBasicMaterial({ color: 0xffff00 }); for (const pts of this.paths) { const pathLineGeo = new THREE.BufferGeometry().setFromPoints(pts); const pathLine = new THREE.Line(pathLineGeo, pathLineMat); pathLine.position.y = 0.01; this.scene.add(pathLine); } } addSegment(a, b) { const seg = new THREE.Vector3().subVectors(b, a); const len = seg.length(); if (len <= 0.0001) return; const mid = new THREE.Vector3().addVectors(a, b).multiplyScalar(0.5); const roadGeo = new THREE.BoxGeometry(len, 0.1, ROAD_HALF_WIDTH * 2); const road = new THREE.Mesh(roadGeo, this.roadMat); road.castShadow = false; road.receiveShadow = true; road.position.set(mid.x, 0.05, mid.z); const angle = Math.atan2(seg.z, seg.x); road.rotation.y = -angle; this.scene.add(road); this.roadMeshes.push(road); } addCornerTile(center) { const size = ROAD_HALF_WIDTH * 2; const geo = new THREE.BoxGeometry(size, 0.101, size); const tile = new THREE.Mesh(geo, this.roadMat); tile.castShadow = false; tile.receiveShadow = true; tile.position.set(center.x, 0.0505, center.z); this.scene.add(tile); this.roadMeshes.push(tile); } }