This commit is contained in:
Dongho Kim
2025-12-17 16:42:23 +09:00
parent 8edb92b25d
commit 4b606e28da
12 changed files with 991 additions and 195 deletions

View File

@@ -34,18 +34,23 @@ use labels::update_labels;
use pipelines::{
Vertex,
ColoredVertex,
create_simple_pipeline,
create_building_pipeline,
RoadVertex,
create_colored_building_pipeline,
create_water_pipeline,
create_water_line_pipeline,
create_road_motorway_outline_pipeline,
create_road_motorway_pipeline,
create_road_primary_outline_pipeline,
create_road_primary_pipeline,
create_road_secondary_outline_pipeline,
create_road_secondary_pipeline,
create_road_residential_outline_pipeline,
create_road_residential_pipeline,
create_landuse_green_pipeline,
create_landuse_residential_pipeline,
create_sand_pipeline,
create_railway_pipeline,
generate_road_geometry,
};
#[wasm_bindgen(start)]
@@ -79,13 +84,40 @@ pub async fn run() {
None,
).await.unwrap();
let size = window.inner_size();
let win = web_sys::window().unwrap();
let dpr = win.device_pixel_ratio();
let inner_width = win.inner_width().unwrap().as_f64().unwrap();
let inner_height = win.inner_height().unwrap().as_f64().unwrap();
let width = (inner_width * dpr) as u32;
let height = (inner_height * dpr) as u32;
let max_dim = device.limits().max_texture_dimension_2d;
let width = size.width.max(1).min(max_dim);
let height = size.height.max(1).min(max_dim);
let width = width.max(1).min(max_dim);
let height = height.max(1).min(max_dim);
// Explicitly resize the canvas/backing store to match physical pixels
if let Some(canvas) = window.canvas() {
canvas.set_width(width);
canvas.set_height(height);
}
let mut config = surface.get_default_config(&adapter, width, height).unwrap();
surface.configure(&device, &config);
// Initial MSAA Texture
let mut msaa_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Multisampled Texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 4,
dimension: wgpu::TextureDimension::D2,
format: config.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let mut msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
// Initial Camera Setup
let camera = Arc::new(Mutex::new(Camera {
x: 0.5307617,
@@ -309,14 +341,23 @@ pub async fn run() {
}
// Create Pipelines
let building_pipeline = create_building_pipeline(&device, &config.format, &camera_bind_group_layout);
let building_pipeline = create_colored_building_pipeline(&device, &config.format, &camera_bind_group_layout);
let water_pipeline = create_water_pipeline(&device, &config.format, &camera_bind_group_layout);
let water_line_pipeline = create_water_line_pipeline(&device, &config.format, &camera_bind_group_layout);
let railway_pipeline = create_railway_pipeline(&device, &config.format, &camera_bind_group_layout);
let motorway_pipeline = create_road_motorway_pipeline(&device, &config.format, &camera_bind_group_layout);
let primary_pipeline = create_road_primary_pipeline(&device, &config.format, &camera_bind_group_layout);
let secondary_pipeline = create_road_secondary_pipeline(&device, &config.format, &camera_bind_group_layout);
let residential_pipeline = create_road_residential_pipeline(&device, &config.format, &camera_bind_group_layout);
// Road Pipelines (Outline & Fill)
let motorway_outline = create_road_motorway_outline_pipeline(&device, &config.format, &camera_bind_group_layout);
let motorway_fill = create_road_motorway_pipeline(&device, &config.format, &camera_bind_group_layout);
let primary_outline = create_road_primary_outline_pipeline(&device, &config.format, &camera_bind_group_layout);
let primary_fill = create_road_primary_pipeline(&device, &config.format, &camera_bind_group_layout);
let secondary_outline = create_road_secondary_outline_pipeline(&device, &config.format, &camera_bind_group_layout);
let secondary_fill = create_road_secondary_pipeline(&device, &config.format, &camera_bind_group_layout);
let residential_outline = create_road_residential_outline_pipeline(&device, &config.format, &camera_bind_group_layout);
let residential_fill = create_road_residential_pipeline(&device, &config.format, &camera_bind_group_layout);
let landuse_green_pipeline = create_landuse_green_pipeline(&device, &config.format, &camera_bind_group_layout);
let landuse_residential_pipeline = create_landuse_residential_pipeline(&device, &config.format, &camera_bind_group_layout);
let sand_pipeline = create_sand_pipeline(&device, &config.format, &camera_bind_group_layout);
@@ -327,13 +368,41 @@ pub async fn run() {
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(new_size) => {
WindowEvent::Resized(_) => { // Ignore winit size, calculate manually
let win = web_sys::window().unwrap();
let dpr = win.device_pixel_ratio();
let inner_width = win.inner_width().unwrap().as_f64().unwrap();
let inner_height = win.inner_height().unwrap().as_f64().unwrap();
let width = (inner_width * dpr) as u32;
let height = (inner_height * dpr) as u32;
let max_dim = device.limits().max_texture_dimension_2d;
let width = new_size.width.max(1).min(max_dim);
let height = new_size.height.max(1).min(max_dim);
let width = width.max(1).min(max_dim);
let height = height.max(1).min(max_dim);
// Explicitly resize the canvas/backing store to match physical pixels
if let Some(canvas) = window.canvas() {
canvas.set_width(width);
canvas.set_height(height);
}
config.width = width;
config.height = height;
surface.configure(&device, &config);
// Recreate MSAA texture
msaa_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Multisampled Texture"),
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 4,
dimension: wgpu::TextureDimension::D2,
format: config.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
camera.lock().unwrap().aspect = width as f32 / height as f32;
window.request_redraw();
}
@@ -412,12 +481,12 @@ pub async fn run() {
for tile in tiles_to_process {
// Build vertex data for each feature type
let mut road_motorway_vertex_data: Vec<Vertex> = Vec::new();
let mut road_primary_vertex_data: Vec<Vertex> = Vec::new();
let mut road_secondary_vertex_data: Vec<Vertex> = Vec::new();
let mut road_residential_vertex_data: Vec<Vertex> = Vec::new();
let mut building_vertex_data: Vec<Vertex> = Vec::new();
// Build vertex data for each feature type - roads use RoadVertex
let mut road_motorway_vertex_data: Vec<RoadVertex> = Vec::new();
let mut road_primary_vertex_data: Vec<RoadVertex> = Vec::new();
let mut road_secondary_vertex_data: Vec<RoadVertex> = Vec::new();
let mut road_residential_vertex_data: Vec<RoadVertex> = Vec::new();
let mut building_vertex_data: Vec<ColoredVertex> = Vec::new();
let mut landuse_green_vertex_data: Vec<Vertex> = Vec::new();
let mut landuse_residential_vertex_data: Vec<Vertex> = Vec::new();
let mut landuse_sand_vertex_data: Vec<Vertex> = Vec::new();
@@ -425,47 +494,99 @@ pub async fn run() {
let mut railway_vertex_data: Vec<ColoredVertex> = Vec::new();
let mut water_line_vertex_data: Vec<Vertex> = Vec::new();
// Process ways (roads)
// Process ways (roads) - generate quad geometry for zoom-aware width
if let Some(ways) = state_guard.ways.get(&tile) {
for way in ways {
let highway = way.tags.get("highway").map(|s| s.as_str());
if highway.is_none() { continue; }
let highway_type = highway.unwrap();
let target = match highway_type {
// Road type: 0=motorway, 1=primary, 2=secondary, 3=residential
let road_type: f32 = match highway_type {
"motorway" | "motorway_link" | "trunk" | "trunk_link" => 0.0,
"primary" | "primary_link" => 1.0,
"secondary" | "secondary_link" => 2.0,
_ => 3.0,
};
let target: &mut Vec<RoadVertex> = match highway_type {
"motorway" | "motorway_link" | "trunk" | "trunk_link" => &mut road_motorway_vertex_data,
"primary" | "primary_link" => &mut road_primary_vertex_data,
"secondary" | "secondary_link" => &mut road_secondary_vertex_data,
_ => &mut road_residential_vertex_data,
};
// Parse all points first
let mut road_points: Vec<Vertex> = Vec::new();
// Parse lanes (default based on road type)
let default_lanes: f32 = match highway_type {
"motorway" | "trunk" => 4.0,
"motorway_link" | "trunk_link" | "primary" => 2.0,
_ => 2.0,
};
let lanes: f32 = way.tags.get("lanes")
.and_then(|s| s.parse().ok())
.unwrap_or(default_lanes);
// Parse all road centerline points
let mut centers: Vec<[f32; 2]> = Vec::new();
for chunk in way.points.chunks(8) {
if chunk.len() < 8 { break; }
let lat = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4]));
let lon = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4]));
let (x, y) = project(lat as f64, lon as f64);
road_points.push(Vertex { position: [x, y] });
centers.push([x, y]);
}
// For LineList: push pairs of vertices for each segment (A-B, B-C, C-D, ...)
for i in 0..road_points.len().saturating_sub(1) {
target.push(road_points[i]);
target.push(road_points[i + 1]);
}
// Generate connected geometry with miter joins
let geom = pipelines::roads::generate_road_geometry(&centers, lanes, road_type);
target.extend(geom);
}
}
// Process buildings
// Process buildings with type-based colors
if let Some(buildings) = state_guard.buildings.get(&tile) {
for building in buildings {
// Get building type from tags
let building_type = building.tags.get("building").map(|s| s.as_str()).unwrap_or("yes");
// Also check amenity tag for public buildings (hospitals, schools, etc.)
let amenity_type = building.tags.get("amenity").map(|s| s.as_str());
let is_public_amenity = matches!(amenity_type,
Some("hospital") | Some("school") | Some("university") | Some("college") |
Some("kindergarten") | Some("library") | Some("place_of_worship") |
Some("community_centre") | Some("townhall") | Some("police") |
Some("fire_station") | Some("courthouse") | Some("embassy")
);
// Assign color based on building type (light theme colors)
let color: [f32; 3] = if is_public_amenity {
// Public/Civic: light gray #e0ddd4
[0.88, 0.87, 0.83]
} else {
match building_type {
// Residential: cream #f2efe9
"house" | "apartments" | "residential" | "detached" | "semidetached_house" | "terrace" | "dormitory" =>
[0.95, 0.94, 0.91],
// Commercial: tan #e8e4dc
"commercial" | "retail" | "office" | "supermarket" | "kiosk" | "hotel" =>
[0.91, 0.89, 0.86],
// Industrial: gray-tan #d9d5cc
"industrial" | "warehouse" | "factory" | "manufacture" =>
[0.85, 0.84, 0.80],
// Public/Civic: light gray #e0ddd4
"school" | "hospital" | "university" | "government" | "public" | "church" | "cathedral" | "mosque" | "synagogue" | "temple" | "train_station" | "fire_station" | "police" | "library" | "kindergarten" =>
[0.88, 0.87, 0.83],
// Default: gray #d9d9d9
_ => [0.85, 0.85, 0.85],
}
};
for chunk in building.points.chunks(8) {
if chunk.len() < 8 { break; }
let lat = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4]));
let lon = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4]));
let (x, y) = project(lat as f64, lon as f64);
building_vertex_data.push(Vertex { position: [x, y] });
building_vertex_data.push(ColoredVertex { position: [x, y], color });
}
}
}
@@ -732,16 +853,16 @@ pub async fn run() {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
view: &msaa_view,
resolve_target: Some(&view),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: if is_dark { 0.05 } else { 0.95 },
g: if is_dark { 0.05 } else { 0.95 },
b: if is_dark { 0.05 } else { 0.95 },
r: if is_dark { 0.05 } else { 0.957 },
g: if is_dark { 0.05 } else { 0.957 },
b: if is_dark { 0.05 } else { 0.957 },
a: 1.0,
}),
store: wgpu::StoreOp::Store,
store: wgpu::StoreOp::Discard,
},
})],
depth_stencil_attachment: None,
@@ -799,9 +920,56 @@ pub async fn run() {
}
}
// 4. Draw Roads (Layered by importance)
// Residential
rpass.set_pipeline(&residential_pipeline);
// 4. Draw Roads (Layered by importance with Outline/Fill pass for seamless joins)
// --- PASS 1: OUTLINES (Bottom Layer) ---
// Order: Residential -> Secondary -> Primary -> Motorway
// Residential Outline
// rpass.set_pipeline(&residential_outline);
// rpass.set_bind_group(0, &camera_bind_group, &[]);
// for buffers in &tiles_to_render {
// if buffers.road_residential_vertex_count > 0 {
// rpass.set_vertex_buffer(0, buffers.road_residential_vertex_buffer.slice(..));
// rpass.draw(0..buffers.road_residential_vertex_count, 0..1);
// }
// }
// Secondary Outline
// rpass.set_pipeline(&secondary_outline);
// rpass.set_bind_group(0, &camera_bind_group, &[]);
// for buffers in &tiles_to_render {
// if buffers.road_secondary_vertex_count > 0 {
// rpass.set_vertex_buffer(0, buffers.road_secondary_vertex_buffer.slice(..));
// rpass.draw(0..buffers.road_secondary_vertex_count, 0..1);
// }
// }
// Primary Outline
// rpass.set_pipeline(&primary_outline);
// rpass.set_bind_group(0, &camera_bind_group, &[]);
// for buffers in &tiles_to_render {
// if buffers.road_primary_vertex_count > 0 {
// rpass.set_vertex_buffer(0, buffers.road_primary_vertex_buffer.slice(..));
// rpass.draw(0..buffers.road_primary_vertex_count, 0..1);
// }
// }
// Motorway Outline
// rpass.set_pipeline(&motorway_outline);
// rpass.set_bind_group(0, &camera_bind_group, &[]);
// for buffers in &tiles_to_render {
// if buffers.road_motorway_vertex_count > 0 {
// rpass.set_vertex_buffer(0, buffers.road_motorway_vertex_buffer.slice(..));
// rpass.draw(0..buffers.road_motorway_vertex_count, 0..1);
// }
// }
// --- PASS 2: FILLS (Top Layer) ---
// Order: Residential -> Secondary -> Primary -> Motorway
// Residential Fill
rpass.set_pipeline(&residential_fill);
rpass.set_bind_group(0, &camera_bind_group, &[]);
for buffers in &tiles_to_render {
if buffers.road_residential_vertex_count > 0 {
@@ -809,8 +977,9 @@ pub async fn run() {
rpass.draw(0..buffers.road_residential_vertex_count, 0..1);
}
}
// Secondary
rpass.set_pipeline(&secondary_pipeline);
// Secondary Fill
rpass.set_pipeline(&secondary_fill);
rpass.set_bind_group(0, &camera_bind_group, &[]);
for buffers in &tiles_to_render {
if buffers.road_secondary_vertex_count > 0 {
@@ -818,8 +987,9 @@ pub async fn run() {
rpass.draw(0..buffers.road_secondary_vertex_count, 0..1);
}
}
// Primary
rpass.set_pipeline(&primary_pipeline);
// Primary Fill
rpass.set_pipeline(&primary_fill);
rpass.set_bind_group(0, &camera_bind_group, &[]);
for buffers in &tiles_to_render {
if buffers.road_primary_vertex_count > 0 {
@@ -827,8 +997,9 @@ pub async fn run() {
rpass.draw(0..buffers.road_primary_vertex_count, 0..1);
}
}
// Motorway
rpass.set_pipeline(&motorway_pipeline);
// Motorway Fill
rpass.set_pipeline(&motorway_fill);
rpass.set_bind_group(0, &camera_bind_group, &[]);
for buffers in &tiles_to_render {
if buffers.road_motorway_vertex_count > 0 {