Spaces:
Running
Running
| 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); | |
| } | |
| } | |