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); | |
} | |
} | |