Spaces:
Running
Running
| import * as THREE from "three"; | |
| import { PATH_POINTS, PATHS, GRID_CELL_SIZE } from "../config/gameConfig.js"; | |
| // Snap value to grid (to nearest grid line) | |
| export function snapToGrid(value, size = GRID_CELL_SIZE) { | |
| return Math.round(value / size) * size; | |
| } | |
| // Grid helpers to work with cell centers | |
| export function worldToCell(x, z, size = GRID_CELL_SIZE) { | |
| // Map world coords to integer col/row indices | |
| const col = Math.floor(x / size); | |
| const row = Math.floor(z / size); | |
| return { col, row }; | |
| } | |
| export function cellToWorldCenter(col, row, size = GRID_CELL_SIZE) { | |
| // Center of the tile: (col + 0.5, row + 0.5) * size | |
| return new THREE.Vector3((col + 0.5) * size, 0, (row + 0.5) * size); | |
| } | |
| // Check if point is on road | |
| export function isOnRoad(p, pathPoints = PATH_POINTS) { | |
| const halfWidth = 1.6; | |
| // If branching paths are defined, check against all of them | |
| if (Array.isArray(PATHS) && PATHS.length > 0) { | |
| for (const pts of PATHS) { | |
| for (let i = 0; i < pts.length - 1; i++) { | |
| if (pointSegmentDistance2D(p, pts[i], pts[i + 1]) <= halfWidth) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| // Fallback to single path | |
| for (let i = 0; i < pathPoints.length - 1; i++) { | |
| const a = pathPoints[i]; | |
| const b = pathPoints[i + 1]; | |
| if (pointSegmentDistance2D(p, a, b) <= halfWidth) return true; | |
| } | |
| return false; | |
| } | |
| // Calculate distance from point to line segment in 2D | |
| export function pointSegmentDistance2D(p, a, b) { | |
| const apx = p.x - a.x, | |
| apz = p.z - a.z; | |
| const abx = b.x - a.x, | |
| abz = b.z - a.z; | |
| const abLenSq = abx * abx + abz * abz; | |
| let t = 0; | |
| if (abLenSq > 0) t = (apx * abx + apz * abz) / abLenSq; | |
| t = Math.max(0, Math.min(1, t)); | |
| const cx = a.x + t * abx, | |
| cz = a.z + t * abz; | |
| const dx = p.x - cx, | |
| dz = p.z - cz; | |
| return Math.hypot(dx, dz); | |
| } | |
| // Simple particle/hit effect system | |
| export class EffectSystem { | |
| constructor(scene) { | |
| this.scene = scene; | |
| this.effects = []; | |
| } | |
| spawnHitEffect(pos) { | |
| const geo = new THREE.SphereGeometry(0.2, 6, 6); | |
| const mat = new THREE.MeshBasicMaterial({ | |
| color: 0xfff176, | |
| transparent: true, | |
| opacity: 0.9, | |
| }); | |
| const m = new THREE.Mesh(geo, mat); | |
| m.position.copy(pos); | |
| this.scene.add(m); | |
| this.effects.push({ mesh: m, life: 0.25 }); | |
| } | |
| update(dt) { | |
| for (let i = this.effects.length - 1; i >= 0; i--) { | |
| const e = this.effects[i]; | |
| e.life -= dt; | |
| e.mesh.scale.addScalar(6 * dt); | |
| e.mesh.material.opacity = Math.max(0, e.life / 0.25); | |
| if (e.life <= 0) { | |
| this.scene.remove(e.mesh); | |
| this.effects.splice(i, 1); | |
| } | |
| } | |
| } | |
| } | |