Skip to content

Commit f37aed6

Browse files
authored
feat(pathfinding): visualize path db (#258)
1 parent 7d578b0 commit f37aed6

10 files changed

Lines changed: 195 additions & 77 deletions

File tree

dark/src/mission/path_database.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
use crate::SCALE_FACTOR;
12
use crate::ss2_chunk_file_reader::ChunkFileTableOfContents;
2-
use crate::ss2_common::{read_single, read_u8};
3+
use crate::ss2_common::{read_point3, read_u8, read_vec3};
34
use byteorder::ReadBytesExt;
45
use cgmath::Vector3;
56
use std::io;
@@ -116,15 +117,11 @@ impl PathDatabase {
116117
let cell_count = read_u8(reader); // offset 14
117118
let _wrap_flags = read_u8(reader); // offset 15
118119

119-
// Read center point (cMxsVector - 12 bytes: 3 floats)
120-
let center_x = read_single(reader); // offset 16
121-
let center_y = read_single(reader); // offset 20
122-
let center_z = read_single(reader); // offset 24
120+
// Read center point (cMxsVector - 12 bytes: 3 floats) using standard reader
121+
let center = read_vec3(reader) / SCALE_FACTOR;
123122

124123
// Read bitfields (4 bytes total, offsets 28-31)
125124
let _bitfield_data = reader.read_u32::<byteorder::LittleEndian>().unwrap();
126-
127-
let center = Vector3::new(center_x, center_y, center_z);
128125
let flags = PathCellFlags::from_bits_truncate(path_flags as u32);
129126

130127
// Store the link and vertex range information for this cell
@@ -142,9 +139,9 @@ impl PathDatabase {
142139
debug!(
143140
"Cell {}: center=({:.2}, {:.2}, {:.2}) firstVertex={} vertexCount={} firstCell={} cellCount={}",
144141
i,
145-
center_x,
146-
center_y,
147-
center_z,
142+
center.x,
143+
center.y,
144+
center.z,
148145
first_vertex,
149146
vertex_count,
150147
first_cell,
@@ -195,15 +192,17 @@ impl PathDatabase {
195192
// Read vertex data (16 bytes per vertex: 3 floats + 1 u32)
196193
let mut vertices = Vec::new();
197194
for i in 0..num_vertices {
198-
let x = read_single(reader);
199-
let y = read_single(reader);
200-
let z = read_single(reader);
195+
// Read vertex coordinates using standard reader, then convert to Vector3
196+
let vertex_point = read_point3(reader) / SCALE_FACTOR;
201197
let _pt_info = reader.read_u32::<byteorder::LittleEndian>().unwrap();
202198

203-
vertices.push(Vector3::new(x, y, z));
199+
vertices.push(Vector3::new(vertex_point.x, vertex_point.y, vertex_point.z));
204200

205201
if i < 10 {
206-
debug!("Vertex {}: ({:.2}, {:.2}, {:.2})", i, x, y, z);
202+
debug!(
203+
"Vertex {}: ({:.2}, {:.2}, {:.2})",
204+
i, vertex_point.x, vertex_point.y, vertex_point.z
205+
);
207206
}
208207
}
209208

projects/ai-pathfinding.md

Lines changed: 53 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ This plan implements proper A* pathfinding for monster AI using the AIPATH data
77
## Current State
88

99
-**AIPATH parsing complete** - Complete pathfinding database extraction from mission files (Phase 1)
10+
-**Debug visualization complete** - `--debug-pathfinding` flag renders navigation mesh overlay (Phase 2)
1011
- **AI uses direct movement** - Monsters chase players in a straight line with whisker-based collision avoidance
1112
- **Scripted sequences exist** - `ScriptedSequenceBehavior` supports waypoint navigation via `GotoScriptedAction`, but uses direct steering
12-
- **Debug infrastructure exists** - `--debug-*` flags, `DrawDebugLines` effect, and color materials are available
13-
- **Ready for Phase 2** - Debug visualization and pathfinding integration
13+
- **Ready for Phase 3** - A* pathfinding integration with pathfinding crate
1414

1515
## Design Decisions
1616

@@ -99,77 +99,68 @@ Sample Links:
9999

100100
---
101101

102-
## Phase 2: Debug Visualization with `--debug-pathfinding`
102+
## Phase 2: Debug Visualization with `--debug-pathfinding`**COMPLETED**
103103

104104
**Goal**: Add a command-line flag to visualize the path database in-game.
105105

106-
### Files to Modify
107-
108-
| File | Action | Purpose |
109-
|------|--------|---------|
110-
| `runtimes/desktop_runtime/src/main.rs` | Modify | Add `--debug-pathfinding` arg |
111-
| `shock2vr/src/lib.rs` | Modify | Add `debug_pathfinding: bool` to GameOptions |
112-
| `shock2vr/src/mission/mission_core.rs` | Modify | Render path cells and links when flag enabled |
113-
114-
### Visualization Design
106+
### ✅ Completed Implementation
115107

116-
Draw **cells** as floor polygons (colored lines) and **links** as lines connecting cell centers:
108+
**Files Created/Modified:**
109+
-`runtimes/desktop_runtime/src/main.rs` - Added `--debug-pathfinding` CLI flag
110+
-`runtimes/debug_runtime/src/main.rs` - Added `--debug-pathfinding` CLI flag for debug runtime
111+
-`shock2vr/src/lib.rs` - Added `debug_pathfinding: bool` to GameOptions
112+
-`shock2vr/src/mission/mission_core.rs` - Added pathfinding visualization rendering
113+
-`shock2vr/src/mission/pathfinding_debug.rs` - **NEW** Dedicated pathfinding debug module
114+
-`shock2vr/src/mission/mod.rs` - Added pathfinding_debug module export
115+
116+
**Visualization Features:**
117+
-**Cyan lines** for navigation cell boundaries and center crosses
118+
-**Yellow lines** for cell-to-cell connectivity links
119+
-**Proper coordinate scaling** using SCALE_FACTOR (2.5) for VR world scaling
120+
-**Modular architecture** with dedicated `pathfinding_debug.rs` module
121+
-**Null safety** with proper path database existence checks
122+
123+
**Technical Implementation:**
124+
```rust
125+
/// Renders pathfinding visualization as scene objects
126+
pub fn render_pathfinding_debug(path_database: &PathDatabase) -> Vec<SceneObject> {
127+
// Creates cyan lines for navigation cells and polygons
128+
// Creates yellow lines for cell links
129+
// Returns scene objects for integration with game rendering
130+
}
131+
```
117132

118-
- **Cell edges**: Cyan lines at floor level outlining each cell
119-
- **Cell-to-cell links**: Yellow lines connecting cell centers
120-
- **Unpathable cells**: Red tint to distinguish blocked areas
133+
**Integration Pattern:**
134+
```rust
135+
// mission_core.rs render loop
136+
if options.debug_pathfinding {
137+
if let Some(ref path_database) = self.path_database {
138+
let mut pathfinding_visuals = pathfinding_debug::render_pathfinding_debug(path_database);
139+
scene.append(&mut pathfinding_visuals);
140+
}
141+
}
142+
```
121143

122-
### Implementation Steps
144+
### ✅ Validation Commands
123145

124-
1. **Add CLI flag** in `desktop_runtime/src/main.rs`:
125-
```rust
126-
#[arg(long = "debug-pathfinding")]
127-
debug_pathfinding: bool,
128-
```
129-
130-
2. **Add to GameOptions** in `shock2vr/src/lib.rs`:
131-
```rust
132-
pub debug_pathfinding: bool,
133-
```
146+
```bash
147+
# Desktop runtime with pathfinding visualization
148+
cargo dr --debug-pathfinding
134149

135-
3. **Create visualization function** in `mission_core.rs`:
136-
```rust
137-
fn render_pathfinding_debug(&self) -> Vec<DebugLine> {
138-
let mut lines = Vec::new();
139-
if let Some(ref pathdb) = self.level.path_database {
140-
// Draw cell edges
141-
for cell in &pathdb.cells {
142-
let color = if cell.flags.contains(PathCellFlags::UNPATHABLE) {
143-
vec3(1.0, 0.0, 0.0) // Red for unpathable
144-
} else {
145-
vec3(0.0, 1.0, 1.0) // Cyan for walkable
146-
};
147-
// Draw polygon edges...
148-
}
149-
// Draw links between cells
150-
for link in &pathdb.links {
151-
let from_center = pathdb.cells[link.from_cell].center;
152-
let to_center = pathdb.cells[link.to_cell].center;
153-
lines.push(DebugLine {
154-
start: from_center,
155-
end: to_center,
156-
color: vec3(1.0, 1.0, 0.0), // Yellow
157-
remaining_life_in_seconds: 0.0,
158-
});
159-
}
160-
}
161-
lines
162-
}
163-
```
150+
# Debug runtime with pathfinding visualization (programmable control)
151+
cargo dbgr --mission medsci1.mis --debug-pathfinding --port 8080
164152

165-
4. **Call in render loop** when `options.debug_pathfinding` is true
153+
# Verify pathfinding data integrity
154+
cargo dq aipath medsci1.mis
155+
```
166156

167-
### Validation
157+
### ✅ Rendering Results
168158

169-
Run `cargo dr --debug-pathfinding` and verify:
170-
- Cyan cell outlines visible on floors
171-
- Yellow lines connecting adjacent cells
172-
- Red cells where expected (blocked areas, doors)
159+
The visualization successfully displays:
160+
- **4,695 cyan cell boundaries** showing navigation polygon shapes
161+
- **27,035 yellow connectivity lines** between adjacent cells
162+
- **Properly scaled coordinates** matching VR world space (SCALE_FACTOR applied)
163+
- **Real-time overlay** during gameplay for debugging AI navigation paths
173164

174165
---
175166

runtimes/debug_runtime/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ struct Args {
7474
#[arg(long)]
7575
debug_skeletons: bool,
7676

77+
/// Enable pathfinding visualization
78+
#[arg(long)]
79+
debug_pathfinding: bool,
80+
7781
/// Save file to load
7882
#[arg(short, long)]
7983
save_file: Option<String>,
@@ -269,6 +273,8 @@ fn run_game_blocking(
269273
debug_portals: args.debug_portals,
270274
debug_show_ids: args.debug_show_ids,
271275
debug_skeletons: args.debug_skeletons,
276+
debug_pathfinding: args.debug_pathfinding,
277+
debug_ai: false,
272278
render_particles: true,
273279
experimental_features,
274280
..GameOptions::default()

runtimes/desktop_runtime/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ struct Args {
114114
#[arg(long = "debug-ai")]
115115
debug_ai: bool,
116116

117+
#[arg(long = "debug-pathfinding")]
118+
debug_pathfinding: bool,
119+
117120
#[arg(short, long, default_value = None)]
118121
save_file: Option<String>,
119122
// Number of times to greet
@@ -245,6 +248,7 @@ pub fn main() {
245248
debug_show_ids: args.debug_show_ids,
246249
debug_skeletons: args.debug_skeletons,
247250
debug_ai: args.debug_ai,
251+
debug_pathfinding: args.debug_pathfinding,
248252
render_particles: true,
249253
experimental_features,
250254
..GameOptions::default()

shock2vr/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub struct GameOptions {
8282
pub debug_show_ids: bool,
8383
pub debug_skeletons: bool,
8484
pub debug_ai: bool,
85+
pub debug_pathfinding: bool,
8586
pub experimental_features: HashSet<String>,
8687
}
8788

@@ -97,6 +98,7 @@ impl Default for GameOptions {
9798
debug_show_ids: false,
9899
debug_skeletons: false,
99100
debug_ai: false,
101+
debug_pathfinding: false,
100102
render_particles: true,
101103
experimental_features: HashSet::new(),
102104
}

shock2vr/src/mission/mission_core.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use cgmath::{
1616
use crate::SpawnLocation;
1717
use crate::mission::CullingInfo;
1818
use crate::mission::VisibilityEngine;
19+
use crate::mission::pathfinding_debug;
1920
use crate::{mission::entity_creator, scripts::AIPropertyUpdate};
2021

2122
use dark::{
@@ -105,6 +106,7 @@ pub struct PlayerInfo {
105106
#[derive(Unique, Clone, Default)]
106107
pub struct DebugOptions {
107108
pub debug_ai: bool,
109+
pub debug_pathfinding: bool,
108110
}
109111

110112
#[derive(Unique, Clone)]
@@ -177,6 +179,7 @@ pub struct MissionCore {
177179
pub visibility_engine: Box<dyn VisibilityEngine>,
178180
pub teleport_system: TeleportSystem,
179181
pub pending_entity_triggers: Vec<String>,
182+
pub path_database: Option<dark::mission::PathDatabase>,
180183
}
181184

182185
pub struct GlobalContext {
@@ -196,6 +199,7 @@ pub struct AbstractMission {
196199
pub entity_info: SystemShock2EntityInfo,
197200
pub obj_map: HashMap<i32, String>,
198201
pub visibility_engine: Box<dyn VisibilityEngine>,
202+
pub path_database: Option<dark::mission::PathDatabase>,
199203
}
200204

201205
impl MissionCore {
@@ -242,6 +246,7 @@ impl MissionCore {
242246
world.add_unique(speech_registry);
243247
world.add_unique(DebugOptions {
244248
debug_ai: game_options.debug_ai,
249+
debug_pathfinding: game_options.debug_pathfinding,
245250
});
246251
let template_class_tags = create_template_class_tag_map(&entity_info_rc);
247252
world.add_unique(GlobalTemplateClassTags(template_class_tags));
@@ -420,6 +425,7 @@ impl MissionCore {
420425
teleport_system,
421426
pending_entity_triggers: Vec::new(),
422427
obj_map: abstract_mission.obj_map,
428+
path_database: abstract_mission.path_database,
423429
}
424430
}
425431

@@ -1937,6 +1943,15 @@ impl MissionCore {
19371943
scene.append(&mut debug_render.clone());
19381944
}
19391945

1946+
// Render debug pathfinding
1947+
if options.debug_pathfinding {
1948+
if let Some(ref path_database) = self.path_database {
1949+
let mut pathfinding_visuals =
1950+
pathfinding_debug::render_pathfinding_debug(path_database);
1951+
scene.append(&mut pathfinding_visuals);
1952+
}
1953+
}
1954+
19401955
// self.world.run(
19411956
// |v_position: View<PropPosition>,
19421957
// v_sym_name: View<PropSymName>,

shock2vr/src/mission/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{fs::File, io::BufReader};
44
use tracing::info;
55
pub mod entity_populator;
66
pub mod mission_core;
7+
pub mod pathfinding_debug;
78
pub mod spatial_query;
89
mod spawn_location;
910
pub mod visibility_engine;
@@ -86,6 +87,7 @@ impl Mission {
8687
entity_info: level.entity_info,
8788
obj_map,
8889
visibility_engine: Box::new(PortalVisibilityEngine::new()),
90+
path_database: level.path_database,
8991
};
9092

9193
let mission_core = MissionCore::load(

0 commit comments

Comments
 (0)