tower-defense / src /scene /PathBuilder.js
victor's picture
victor HF Staff
ok
e4d85e6
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);
}
}