152 lines
4.1 KiB
Markdown
152 lines
4.1 KiB
Markdown
# Phase 8-1: AI System — Design Spec
|
|
|
|
## Overview
|
|
|
|
`voltex_ai` crate를 신규 생성한다. 수동 정의 내비메시, A* 패스파인딩, 스티어링 행동을 구현한다.
|
|
|
|
## Scope
|
|
|
|
- NavMesh (수동 삼각형 폴리곤, 인접 정보)
|
|
- A* 패스파인딩 (삼각형 그래프)
|
|
- 스티어링 행동 (Seek, Flee, Arrive, Wander, FollowPath)
|
|
|
|
## Out of Scope
|
|
|
|
- 자동 내비메시 생성 (Recast 스타일 복셀화)
|
|
- String pulling (Funnel algorithm)
|
|
- 동적 장애물 회피
|
|
- ECS 통합 (AI 컴포넌트)
|
|
- 내비메시 직렬화
|
|
|
|
## Module Structure
|
|
|
|
```
|
|
crates/voltex_ai/
|
|
├── Cargo.toml
|
|
└── src/
|
|
├── lib.rs
|
|
├── navmesh.rs — NavMesh, NavTriangle
|
|
├── pathfinding.rs — A* find_path
|
|
└── steering.rs — SteeringAgent, seek/flee/arrive/wander/follow_path
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
- `voltex_math` — Vec3
|
|
|
|
## Types
|
|
|
|
### NavTriangle
|
|
|
|
```rust
|
|
#[derive(Debug, Clone)]
|
|
pub struct NavTriangle {
|
|
pub indices: [usize; 3],
|
|
pub neighbors: [Option<usize>; 3],
|
|
}
|
|
```
|
|
|
|
- `indices` — NavMesh.vertices 인덱스 (CCW)
|
|
- `neighbors[i]` — edge i↔(i+1) 반대편 삼각형. None이면 경계 에지.
|
|
|
|
### NavMesh
|
|
|
|
```rust
|
|
pub struct NavMesh {
|
|
pub vertices: Vec<Vec3>,
|
|
pub triangles: Vec<NavTriangle>,
|
|
}
|
|
```
|
|
|
|
**Methods:**
|
|
- `new(vertices, triangles)` — 생성
|
|
- `find_triangle(point: Vec3) -> Option<usize>` — XZ 평면 투영으로 점이 속한 삼각형 인덱스 반환
|
|
- `triangle_center(tri_idx: usize) -> Vec3` — 삼각형 세 꼭짓점의 중심
|
|
- `edge_midpoint(tri_idx: usize, edge: usize) -> Vec3` — 에지 중점 (공유 에지 통과 지점)
|
|
|
|
### A* Pathfinding
|
|
|
|
```rust
|
|
pub fn find_path(navmesh: &NavMesh, start: Vec3, goal: Vec3) -> Option<Vec<Vec3>>
|
|
```
|
|
|
|
1. `find_triangle(start)`, `find_triangle(goal)` — 시작/목표 삼각형
|
|
2. 둘 다 찾지 못하면 None
|
|
3. 같은 삼각형이면 `vec![start, goal]`
|
|
4. A* on triangle graph:
|
|
- Node = triangle index
|
|
- Neighbors = triangle.neighbors (Some만)
|
|
- g cost = 현재까지 실제 거리 (중심점 간)
|
|
- h cost = 현재 삼각형 중심 → 목표 삼각형 중심 유클리드 거리
|
|
5. 결과: 삼각형 체인 → 각 삼각형 중심점으로 경로 생성
|
|
6. 경로 시작에 start, 끝에 goal 삽입
|
|
|
|
### SteeringAgent
|
|
|
|
```rust
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct SteeringAgent {
|
|
pub position: Vec3,
|
|
pub velocity: Vec3,
|
|
pub max_speed: f32,
|
|
pub max_force: f32,
|
|
}
|
|
```
|
|
|
|
### Steering Functions
|
|
|
|
모두 순수 함수, `Vec3` 조향력 반환 (에이전트 velocity에 더하기 전).
|
|
|
|
```rust
|
|
pub fn seek(agent: &SteeringAgent, target: Vec3) -> Vec3
|
|
```
|
|
- desired = normalize(target - position) * max_speed
|
|
- steering = desired - velocity
|
|
- truncate to max_force
|
|
|
|
```rust
|
|
pub fn flee(agent: &SteeringAgent, threat: Vec3) -> Vec3
|
|
```
|
|
- seek 반대 방향
|
|
|
|
```rust
|
|
pub fn arrive(agent: &SteeringAgent, target: Vec3, slow_radius: f32) -> Vec3
|
|
```
|
|
- distance < slow_radius이면 desired speed = max_speed * (distance / slow_radius)
|
|
- 목표에 매우 가까우면 (< 0.01) 제로
|
|
|
|
```rust
|
|
pub fn wander(agent: &SteeringAgent, wander_radius: f32, wander_distance: f32, angle: f32) -> Vec3
|
|
```
|
|
- agent 전방 wander_distance에 원(wander_radius) 위의 점을 target으로 seek
|
|
- angle은 호출자가 매 프레임 랜덤 변경
|
|
|
|
```rust
|
|
pub fn follow_path(agent: &SteeringAgent, path: &[Vec3], current_waypoint: usize, waypoint_radius: f32) -> (Vec3, usize)
|
|
```
|
|
- current_waypoint에 도달하면 다음으로 전진
|
|
- 마지막 웨이포인트면 arrive
|
|
- 아니면 seek
|
|
- 반환: (steering_force, updated_waypoint_index)
|
|
|
|
## Test Plan
|
|
|
|
### navmesh.rs
|
|
- 정사각형(2 삼각형) 내비메시 생성
|
|
- find_triangle: 내부 점 → Some, 외부 점 → None
|
|
- triangle_center: 정확한 중심
|
|
- edge_midpoint: 공유 에지 중점
|
|
|
|
### pathfinding.rs
|
|
- 같은 삼각형: 직선 경로
|
|
- 인접 삼각형: 2-step 경로
|
|
- 3개 삼각형 체인: 올바른 순서
|
|
- 도달 불가: None
|
|
- start/goal이 navmesh 밖: None
|
|
|
|
### steering.rs
|
|
- seek: 목표 방향
|
|
- flee: 반대 방향
|
|
- arrive: 가까우면 감속, 멀면 max_speed
|
|
- follow_path: 웨이포인트 전진
|