This commit is contained in:
2025-11-26 11:57:23 +01:00
parent 80bdaef5c8
commit 696d058fed
10 changed files with 754 additions and 185 deletions

View File

@@ -133,6 +133,8 @@ struct TileBuffers {
landuse_index_count: u32,
water_vertex_buffer: wgpu::Buffer,
water_index_count: u32,
railway_vertex_buffer: wgpu::Buffer,
railway_vertex_count: u32,
}
struct AppState {
@@ -141,6 +143,7 @@ struct AppState {
buildings: HashMap<(i32, i32, i32), Vec<MapWay>>,
landuse: HashMap<(i32, i32, i32), Vec<MapWay>>,
water: HashMap<(i32, i32, i32), Vec<MapWay>>,
railways: HashMap<(i32, i32, i32), Vec<MapWay>>,
buffers: HashMap<(i32, i32, i32), std::sync::Arc<TileBuffers>>,
loaded_tiles: HashSet<(i32, i32, i32)>,
pending_tiles: HashSet<(i32, i32, i32)>,
@@ -340,6 +343,7 @@ pub async fn run() {
buildings: HashMap::new(),
landuse: HashMap::new(),
water: HashMap::new(),
railways: HashMap::new(),
buffers: HashMap::new(),
loaded_tiles: HashSet::new(),
pending_tiles: HashSet::new(),
@@ -451,6 +455,7 @@ pub async fn run() {
let building_pipeline = create_building_pipeline(&device, &config.format, &camera_bind_group_layout);
let landuse_pipeline = create_landuse_pipeline(&device, &config.format, &camera_bind_group_layout);
let water_pipeline = create_water_pipeline(&device, &config.format, &camera_bind_group_layout);
let railway_pipeline = create_railway_pipeline(&device, &config.format, &camera_bind_group_layout);
let window_clone = window.clone();
@@ -665,9 +670,26 @@ pub async fn run() {
}
}
}
// Process railways
let mut railway_vertex_data = Vec::new();
if let Some(railways) = state_guard.railways.get(tile) {
for way in railways {
if way.points.len() < 2 { continue; }
for i in 0..way.points.len() - 1 {
let p1 = &way.points[i];
let p2 = &way.points[i+1];
let (x1, y1) = project(p1[0], p1[1]);
let (x2, y2) = project(p2[0], p2[1]);
railway_vertex_data.push(Vertex { position: [x1, y1] });
railway_vertex_data.push(Vertex { position: [x2, y2] });
}
}
}
// Only create buffers if we have data
if !point_instance_data.is_empty() || !road_vertex_data.is_empty() || !building_vertex_data.is_empty() || !landuse_vertex_data.is_empty() || !water_vertex_data.is_empty() {
if !point_instance_data.is_empty() || !road_vertex_data.is_empty() || !building_vertex_data.is_empty() || !landuse_vertex_data.is_empty() || !water_vertex_data.is_empty() || !railway_vertex_data.is_empty() {
let point_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tile Instance Buffer"),
contents: bytemuck::cast_slice(&point_instance_data),
@@ -698,6 +720,12 @@ pub async fn run() {
contents: bytemuck::cast_slice(&water_vertex_data),
usage: wgpu::BufferUsages::VERTEX,
});
let railway_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tile Railway Buffer"),
contents: bytemuck::cast_slice(&railway_vertex_data),
usage: wgpu::BufferUsages::VERTEX,
});
state_guard.buffers.insert(*tile, std::sync::Arc::new(TileBuffers {
point_instance_buffer: point_buffer,
@@ -710,6 +738,8 @@ pub async fn run() {
landuse_index_count: landuse_vertex_data.len() as u32,
water_vertex_buffer: water_buffer,
water_index_count: water_vertex_data.len() as u32,
railway_vertex_buffer: railway_buffer,
railway_vertex_count: railway_vertex_data.len() as u32,
}));
}
}
@@ -771,6 +801,16 @@ pub async fn run() {
None
};
// Fetch railways
let url_railways = format!("http://localhost:3000/api/tiles/{}/{}/{}/railways", z, x, y);
let railways_data = if let Some(json) = fetch_cached(&url_railways).await {
serde_json::from_str::<Vec<MapWay>>(&json).ok()
} else {
None
};
let mut guard = state_clone.lock().unwrap();
if let Some(nodes) = nodes_data {
@@ -799,6 +839,15 @@ pub async fn run() {
guard.water.insert((z, x, y), water);
}
if let Some(railways) = railways_data {
if !railways.is_empty() {
web_sys::console::log_1(&format!("Fetched {} railway items for tile {}/{}/{}", railways.len(), z, x, y).into());
}
guard.railways.insert((z, x, y), railways);
}
guard.loaded_tiles.insert((z, x, y));
guard.pending_tiles.remove(&(z, x, y));
@@ -881,6 +930,22 @@ pub async fn run() {
rpass.draw(0..buffers.road_vertex_count, 0..1);
}
// Draw Railways (on top of roads?)
if buffers.railway_vertex_count > 0 {
rpass.set_pipeline(&railway_pipeline);
rpass.set_bind_group(0, &camera_bind_group, &[]);
rpass.set_vertex_buffer(0, buffers.railway_vertex_buffer.slice(..));
rpass.draw(0..buffers.railway_vertex_count, 0..1);
}
// Draw Railways (on top of roads?)
if buffers.railway_vertex_count > 0 {
rpass.set_pipeline(&railway_pipeline);
rpass.set_bind_group(0, &camera_bind_group, &[]);
rpass.set_vertex_buffer(0, buffers.railway_vertex_buffer.slice(..));
rpass.draw(0..buffers.railway_vertex_count, 0..1);
}
// Draw Buildings (middle layer)
if buffers.building_index_count > 0 {
rpass.set_pipeline(&building_pipeline);
@@ -1370,3 +1435,87 @@ fn create_water_pipeline(
multiview: None,
})
}
fn create_railway_pipeline(
device: &wgpu::Device,
format: &wgpu::TextureFormat,
bind_group_layout: &wgpu::BindGroupLayout
) -> wgpu::RenderPipeline {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(r#"
struct CameraUniform {
params: vec4<f32>,
};
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
struct VertexInput {
@location(0) position: vec2<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
let world_pos = model.position;
let x = world_pos.x * camera.params.x + camera.params.z;
let y = world_pos.y * camera.params.y + camera.params.w;
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(0.3, 0.3, 0.3, 1.0); // Dark grey for railways
}
"#)),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Railway Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[
Vertex::desc(),
],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: *format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::LineList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
})
}