File size: 2,302 Bytes
b29710c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import * as THREE from "three";
import { PATH_POINTS, 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;
  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);
      }
    }
  }
}