update
This commit is contained in:
@@ -573,6 +573,51 @@
|
||||
0 0 4px rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* Road type based sizing - larger roads get larger fonts */
|
||||
.label-street-motorway {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.label-street-primary {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.label-street-secondary {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #5e5e63;
|
||||
}
|
||||
|
||||
.label-street-tertiary {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.label-street-residential {
|
||||
font-size: 9px;
|
||||
font-weight: 400;
|
||||
color: #7e7e83;
|
||||
}
|
||||
|
||||
/* One-way direction arrows */
|
||||
.oneway-arrow {
|
||||
position: absolute;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: rgba(100, 100, 100, 0.5);
|
||||
pointer-events: none;
|
||||
text-shadow: none;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .oneway-arrow {
|
||||
color: rgba(180, 180, 180, 0.5);
|
||||
}
|
||||
|
||||
/* Apple Maps-style POI labels */
|
||||
.label-poi {
|
||||
font-size: 11px;
|
||||
@@ -1279,7 +1324,7 @@
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
import init from './wasm.js?v=fixed_labels_v6';
|
||||
import init from './wasm.js?v=fixed_labels_v20';
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
|
||||
@@ -199,29 +199,32 @@ pub fn update_labels(
|
||||
}
|
||||
}
|
||||
|
||||
// Process ways for street labels
|
||||
// Process ways for street labels - deduplicate by name
|
||||
let client_width = window.inner_width().ok().and_then(|v| v.as_f64()).unwrap_or(width);
|
||||
let client_height = window.inner_height().ok().and_then(|v| v.as_f64()).unwrap_or(height);
|
||||
|
||||
// Collect all street ways and group by name to deduplicate
|
||||
use std::collections::HashMap;
|
||||
let mut street_ways: HashMap<String, Vec<(&crate::types::MapWay, i32)>> = HashMap::new();
|
||||
|
||||
for tile in &visible_tiles {
|
||||
if let Some(ways) = state.ways.get(tile) {
|
||||
for way in ways {
|
||||
// Check if road has a name
|
||||
let name: Option<&str> = way.tags.get("name").map(|s| s.as_str());
|
||||
let highway: Option<&str> = way.tags.get("highway").map(|s| s.as_str());
|
||||
|
||||
if let (Some(name), Some(highway_type)) = (name, highway) {
|
||||
// Skip unnamed or minor roads
|
||||
if name.is_empty() { continue; }
|
||||
|
||||
// Zoom filtering
|
||||
// Zoom filtering - only show labels when roads are clearly visible
|
||||
// Higher number = requires more zoom (more zoomed in)
|
||||
let min_zoom = match highway_type {
|
||||
"motorway" | "trunk" => 200.0,
|
||||
"primary" => 500.0,
|
||||
"secondary" => 1500.0,
|
||||
"tertiary" => 3000.0,
|
||||
"residential" | "unclassified" => 6000.0,
|
||||
_ => 10000.0,
|
||||
"motorway" | "trunk" => 5000.0, // Show early (major highways)
|
||||
"primary" => 20000.0, // Medium zoom
|
||||
"secondary" => 50000.0, // Need more zoom
|
||||
"tertiary" => 100000.0, // Even more zoom
|
||||
"residential" | "unclassified" => 300000.0, // Only when very zoomed in
|
||||
_ => 500000.0, // Minor roads - extremely zoomed in
|
||||
};
|
||||
if zoom < min_zoom { continue; }
|
||||
|
||||
@@ -234,64 +237,106 @@ pub fn update_labels(
|
||||
_ => 10,
|
||||
};
|
||||
|
||||
// Parse road points to find midpoint and angle
|
||||
let points = &way.points;
|
||||
if points.len() < 16 { continue; }
|
||||
|
||||
let mut parsed_points: Vec<[f64; 2]> = Vec::new();
|
||||
for chunk in points.chunks(8) {
|
||||
if chunk.len() < 8 { break; }
|
||||
let lat = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4])) as f64;
|
||||
let lon = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4])) as f64;
|
||||
parsed_points.push([lat, lon]);
|
||||
}
|
||||
|
||||
if parsed_points.len() < 2 { continue; }
|
||||
|
||||
// Calculate midpoint
|
||||
let mid_idx = parsed_points.len() / 2;
|
||||
let mid_point = parsed_points[mid_idx];
|
||||
|
||||
// Calculate angle from road direction
|
||||
let p1 = if mid_idx > 0 { parsed_points[mid_idx - 1] } else { parsed_points[0] };
|
||||
let p2 = if mid_idx + 1 < parsed_points.len() { parsed_points[mid_idx + 1] } else { parsed_points[mid_idx] };
|
||||
|
||||
let (x1, y1) = project(p1[0], p1[1]);
|
||||
let (x2, y2) = project(p2[0], p2[1]);
|
||||
let dx = x2 - x1;
|
||||
let dy = -(y2 - y1);
|
||||
|
||||
let mut angle_deg = (dy as f64).atan2(dx as f64).to_degrees();
|
||||
|
||||
// Keep text readable
|
||||
if angle_deg > 90.0 { angle_deg -= 180.0; }
|
||||
if angle_deg < -90.0 { angle_deg += 180.0; }
|
||||
|
||||
// Project midpoint to screen
|
||||
let (mx, my) = project(mid_point[0], mid_point[1]);
|
||||
let cx = mx * uniforms.params[0] + uniforms.params[2];
|
||||
let cy = my * uniforms.params[1] + uniforms.params[3];
|
||||
|
||||
// Clip check
|
||||
if cx < -1.5 || cx > 1.5 || cy < -1.5 || cy > 1.5 { continue; }
|
||||
|
||||
let css_x = (cx as f64 + 1.0) * 0.5 * client_width;
|
||||
let css_y = (1.0 - cy as f64) * 0.5 * client_height;
|
||||
|
||||
candidates.push(LabelCandidate {
|
||||
name: name.to_string(),
|
||||
x: css_x,
|
||||
y: css_y,
|
||||
priority,
|
||||
is_country: false,
|
||||
rotation: angle_deg,
|
||||
label_type: LabelType::Street,
|
||||
category: "street".to_string(),
|
||||
});
|
||||
street_ways.entry(name.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((way, priority));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each unique street name, pick the longest segment as representative
|
||||
for (street_name, ways_with_priority) in street_ways {
|
||||
// Find the way with the most points (longest segment)
|
||||
let best_way = ways_with_priority.iter()
|
||||
.max_by_key(|(way, _)| way.points.len());
|
||||
|
||||
if let Some((way, priority)) = best_way {
|
||||
let points = &way.points;
|
||||
if points.len() < 16 { continue; }
|
||||
|
||||
let mut parsed_points: Vec<[f64; 2]> = Vec::new();
|
||||
for chunk in points.chunks(8) {
|
||||
if chunk.len() < 8 { break; }
|
||||
let lat = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4])) as f64;
|
||||
let lon = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4])) as f64;
|
||||
parsed_points.push([lat, lon]);
|
||||
}
|
||||
|
||||
if parsed_points.len() < 2 { continue; }
|
||||
|
||||
// Calculate midpoint for label placement
|
||||
let mid_idx = parsed_points.len() / 2;
|
||||
let mid_point = parsed_points[mid_idx];
|
||||
|
||||
// Get two points around midpoint for direction calculation
|
||||
let p1_idx = mid_idx.saturating_sub(1);
|
||||
let p2_idx = (mid_idx + 1).min(parsed_points.len() - 1);
|
||||
|
||||
let p1 = parsed_points[p1_idx];
|
||||
let p2 = parsed_points[p2_idx];
|
||||
|
||||
// Project all three points to screen space (0-1 range)
|
||||
let (mx_proj, my_proj) = project(mid_point[0], mid_point[1]);
|
||||
let (x1_proj, y1_proj) = project(p1[0], p1[1]);
|
||||
let (x2_proj, y2_proj) = project(p2[0], p2[1]);
|
||||
|
||||
// Apply camera transformation to get clip space (-1 to 1)
|
||||
let cx = mx_proj * uniforms.params[0] + uniforms.params[2];
|
||||
let cy = my_proj * uniforms.params[1] + uniforms.params[3];
|
||||
let cx1 = x1_proj * uniforms.params[0] + uniforms.params[2];
|
||||
let cy1 = y1_proj * uniforms.params[1] + uniforms.params[3];
|
||||
let cx2 = x2_proj * uniforms.params[0] + uniforms.params[2];
|
||||
let cy2 = y2_proj * uniforms.params[1] + uniforms.params[3];
|
||||
|
||||
// Clip check on midpoint
|
||||
if cx < -1.5 || cx > 1.5 || cy < -1.5 || cy > 1.5 { continue; }
|
||||
|
||||
// Convert to CSS screen coordinates
|
||||
// CSS: origin top-left, x right, y down
|
||||
let css_x = (cx as f64 + 1.0) * 0.5 * client_width;
|
||||
let css_y = (1.0 - cy as f64) * 0.5 * client_height;
|
||||
let css_x1 = (cx1 as f64 + 1.0) * 0.5 * client_width;
|
||||
let css_y1 = (1.0 - cy1 as f64) * 0.5 * client_height;
|
||||
let css_x2 = (cx2 as f64 + 1.0) * 0.5 * client_width;
|
||||
let css_y2 = (1.0 - cy2 as f64) * 0.5 * client_height;
|
||||
|
||||
// Calculate angle in CSS screen coordinates
|
||||
// In CSS: x increases right, y increases down
|
||||
// CSS rotate() is clockwise from positive x-axis
|
||||
let dx = css_x2 - css_x1;
|
||||
let dy = css_y2 - css_y1;
|
||||
|
||||
// atan2(dy, dx) gives angle from positive x-axis
|
||||
// For dy > 0 (going down), we get positive angle, which rotates text clockwise
|
||||
let mut angle_deg = dy.atan2(dx).to_degrees();
|
||||
|
||||
// Keep text readable (flip if upside down: angle between -90 and 90)
|
||||
if angle_deg > 90.0 { angle_deg -= 180.0; }
|
||||
if angle_deg < -90.0 { angle_deg += 180.0; }
|
||||
|
||||
// Determine highway type for CSS class (for font sizing)
|
||||
let highway_type = way.tags.get("highway").map(|s| s.as_str()).unwrap_or("residential");
|
||||
let road_class = match highway_type {
|
||||
"motorway" | "trunk" => "motorway",
|
||||
"primary" => "primary",
|
||||
"secondary" => "secondary",
|
||||
"tertiary" => "tertiary",
|
||||
_ => "residential",
|
||||
};
|
||||
|
||||
candidates.push(LabelCandidate {
|
||||
name: street_name,
|
||||
x: css_x,
|
||||
y: css_y,
|
||||
priority: *priority,
|
||||
is_country: false,
|
||||
rotation: angle_deg,
|
||||
label_type: LabelType::Street,
|
||||
category: road_class.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Sort by Priority (High to Low)
|
||||
candidates.sort_by(|a, b| b.priority.cmp(&a.priority));
|
||||
@@ -334,7 +379,7 @@ pub fn update_labels(
|
||||
let class_name = match candidate.label_type {
|
||||
LabelType::Country => "label label-country".to_string(),
|
||||
LabelType::City => "label label-city".to_string(),
|
||||
LabelType::Street => "label label-street".to_string(),
|
||||
LabelType::Street => format!("label label-street label-street-{}", candidate.category),
|
||||
LabelType::Poi => format!("label label-poi label-poi-{}", candidate.category),
|
||||
};
|
||||
|
||||
|
||||
@@ -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(¢ers, 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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Building render pipeline
|
||||
|
||||
use super::common::Vertex;
|
||||
use super::common::{Vertex, ColoredVertex};
|
||||
|
||||
pub fn create_building_pipeline(
|
||||
device: &wgpu::Device,
|
||||
@@ -35,10 +35,6 @@ pub fn create_building_pipeline(
|
||||
|
||||
let x = world_pos.x * camera.params.x + camera.params.z;
|
||||
let y = world_pos.y * camera.params.y + camera.params.w;
|
||||
|
||||
// Globe Effect: Spherize
|
||||
// let r2 = x*x + y*y;
|
||||
// let w = 1.0 + r2 * 0.5;
|
||||
|
||||
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
|
||||
return out;
|
||||
@@ -48,7 +44,7 @@ pub fn create_building_pipeline(
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Buildings: Light: #d9d9d9 (0.85), Dark: #333333 (0.2)
|
||||
let is_dark = camera.theme.x;
|
||||
let color = mix(vec3<f32>(0.85, 0.85, 0.85), vec3<f32>(0.2, 0.2, 0.2), is_dark);
|
||||
let color = mix(vec3<f32>(0.92, 0.91, 0.90), vec3<f32>(0.2, 0.2, 0.2), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
@@ -89,7 +85,102 @@ pub fn create_building_pipeline(
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 4,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Colored building pipeline - uses per-vertex color for different building types
|
||||
pub fn create_colored_building_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>,
|
||||
theme: vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) color: vec3<f32>, // Per-vertex color (light theme)
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_color: vec3<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let x = model.position.x * camera.params.x + camera.params.z;
|
||||
let y = model.position.y * camera.params.y + camera.params.w;
|
||||
|
||||
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
|
||||
out.v_color = model.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Dark theme: darken the light theme color
|
||||
let dark_color = in.v_color * 0.5;
|
||||
let color = mix(in.v_color, dark_color, is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Colored Building Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Colored Building Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[ColoredVertex::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::TriangleList,
|
||||
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 {
|
||||
count: 4,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,6 +52,48 @@ impl ColoredVertex {
|
||||
}
|
||||
}
|
||||
|
||||
/// GPU vertex for roads with center position, normal offset, and lanes
|
||||
/// The shader will apply: final_position = center + normal * width_scale
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct RoadVertex {
|
||||
pub center: [f32; 2], // Road centerline position
|
||||
pub normal: [f32; 2], // Perpendicular offset direction (normalized, ±1 for each side)
|
||||
pub lanes: f32, // Number of lanes (1-8)
|
||||
pub road_type: f32, // 0=motorway, 1=primary, 2=secondary, 3=residential
|
||||
}
|
||||
|
||||
impl RoadVertex {
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<RoadVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2, // center
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x2, // normal
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: (std::mem::size_of::<[f32; 2]>() * 2) as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32, // lanes
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: (std::mem::size_of::<[f32; 2]>() * 2 + std::mem::size_of::<f32>()) as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32, // road_type
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a simple render pipeline with standard configuration
|
||||
pub fn create_simple_pipeline(
|
||||
device: &wgpu::Device,
|
||||
@@ -94,7 +136,62 @@ pub fn create_simple_pipeline(
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 4,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a road render pipeline that uses RoadVertex with lanes attribute
|
||||
pub fn create_road_pipeline(
|
||||
device: &wgpu::Device,
|
||||
format: &wgpu::TextureFormat,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
shader: &wgpu::ShaderModule,
|
||||
label: &str,
|
||||
topology: wgpu::PrimitiveTopology,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some(label),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some(label),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[RoadVertex::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: *format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology,
|
||||
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 {
|
||||
count: 4,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ pub fn create_landuse_green_pipeline(
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Green: Light #cdebb0, Dark #2d4a2d
|
||||
let color = mix(vec3<f32>(0.80, 0.92, 0.69), vec3<f32>(0.18, 0.29, 0.18), is_dark);
|
||||
let color = mix(vec3<f32>(0.804, 0.922, 0.690), vec3<f32>(0.18, 0.29, 0.18), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
|
||||
@@ -7,15 +7,19 @@ pub mod roads;
|
||||
pub mod landuse;
|
||||
pub mod railway;
|
||||
|
||||
pub use common::{Vertex, ColoredVertex, create_simple_pipeline};
|
||||
pub use building::create_building_pipeline;
|
||||
pub use common::{Vertex, ColoredVertex, RoadVertex, create_simple_pipeline, create_road_pipeline};
|
||||
pub use building::{create_building_pipeline, create_colored_building_pipeline};
|
||||
pub use water::{create_water_pipeline, create_water_line_pipeline};
|
||||
pub use roads::{
|
||||
create_road_motorway_pipeline,
|
||||
create_road_primary_pipeline,
|
||||
create_road_secondary_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_road_mesh,
|
||||
generate_road_geometry,
|
||||
};
|
||||
pub use landuse::{create_landuse_green_pipeline, create_landuse_residential_pipeline, create_sand_pipeline};
|
||||
pub use railway::create_railway_pipeline;
|
||||
|
||||
@@ -95,7 +95,11 @@ pub fn create_railway_pipeline(
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 4,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,52 +1,176 @@
|
||||
//! Road render pipelines (motorway, primary, secondary, residential)
|
||||
//! Now split into Outline and Fill passes for correct layering.
|
||||
|
||||
use super::common::{Vertex, create_simple_pipeline};
|
||||
use crate::geo::project;
|
||||
use super::common::{create_road_pipeline};
|
||||
// use crate::geo::project; // Unused
|
||||
|
||||
/// Create road mesh geometry (thick lines as triangles)
|
||||
#[allow(dead_code)]
|
||||
pub fn create_road_mesh(points: &[[f64; 2]], width: f32) -> Vec<Vertex> {
|
||||
// Helper for vector math
|
||||
fn normalize(v: [f32; 2]) -> [f32; 2] {
|
||||
let len = (v[0]*v[0] + v[1]*v[1]).sqrt();
|
||||
if len < 0.00001 { [0.0, 0.0] } else { [v[0]/len, v[1]/len] }
|
||||
}
|
||||
|
||||
fn dot(a: [f32; 2], b: [f32; 2]) -> f32 {
|
||||
a[0]*b[0] + a[1]*b[1]
|
||||
}
|
||||
|
||||
/// Generate properly connected road geometry with miter joins
|
||||
pub fn generate_road_geometry(points: &[[f32; 2]], lanes: f32, road_type: f32) -> Vec<super::common::RoadVertex> {
|
||||
let mut vertices = Vec::new();
|
||||
if points.len() < 2 { return vertices; }
|
||||
|
||||
for i in 0..points.len() - 1 {
|
||||
// Computes normals for each segment
|
||||
let mut segment_normals = Vec::with_capacity(points.len() - 1);
|
||||
for i in 0..points.len()-1 {
|
||||
let p1 = points[i];
|
||||
let p2 = points[i+1];
|
||||
|
||||
// Convert to projected coordinates (0..1)
|
||||
let (x1, y1) = project(p1[0], p1[1]);
|
||||
let (x2, y2) = project(p2[0], p2[1]);
|
||||
|
||||
let dx = x2 - x1;
|
||||
let dy = y2 - y1;
|
||||
let len = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
// Skip invalid segments:
|
||||
// 1. Very short segments that would create degenerate geometry
|
||||
// 2. Segments where width > length (creates giant rectangles instead of roads)
|
||||
if len < 0.00001 || len < (width * 2.0) as f32 { continue; }
|
||||
|
||||
// Normal vector scaled by width
|
||||
let nx = -dy / len * width;
|
||||
let ny = dx / len * width;
|
||||
|
||||
// 4 corners
|
||||
let v1 = Vertex { position: [x1 + nx, y1 + ny] };
|
||||
let v2 = Vertex { position: [x1 - nx, y1 - ny] };
|
||||
let v3 = Vertex { position: [x2 + nx, y2 + ny] };
|
||||
let v4 = Vertex { position: [x2 - nx, y2 - ny] };
|
||||
|
||||
// Triangle 1
|
||||
vertices.push(v1);
|
||||
vertices.push(v2);
|
||||
vertices.push(v3);
|
||||
|
||||
// Triangle 2
|
||||
vertices.push(v2);
|
||||
vertices.push(v4);
|
||||
vertices.push(v3);
|
||||
let dx = p2[0] - p1[0];
|
||||
let dy = p2[1] - p1[1];
|
||||
let len = (dx*dx + dy*dy).sqrt();
|
||||
if len < 0.000001 {
|
||||
segment_normals.push([0.0, 0.0]); // Degenerate
|
||||
} else {
|
||||
segment_normals.push([-dy/len, dx/len]);
|
||||
}
|
||||
}
|
||||
vertices
|
||||
|
||||
// Generate vertices (left/right pairs) for each point
|
||||
// Store temporarily
|
||||
let mut point_pairs = Vec::with_capacity(points.len() * 2);
|
||||
|
||||
for i in 0..points.len() {
|
||||
// Find normal for this vertex (miter)
|
||||
let normal: [f32; 2];
|
||||
let mut miter_len = 1.0f32;
|
||||
|
||||
if i == 0 {
|
||||
normal = segment_normals[0];
|
||||
} else if i == points.len() - 1 {
|
||||
normal = segment_normals[i-1];
|
||||
} else {
|
||||
let n1 = segment_normals[i-1];
|
||||
let n2 = segment_normals[i];
|
||||
|
||||
if dot(n1, n1) == 0.0 { normal = n2; }
|
||||
else if dot(n2, n2) == 0.0 { normal = n1; }
|
||||
else {
|
||||
let sum = [n1[0] + n2[0], n1[1] + n2[1]];
|
||||
let miter = normalize(sum);
|
||||
let d = dot(miter, n1);
|
||||
|
||||
if d.abs() < 0.1 { // Too sharp
|
||||
normal = n1;
|
||||
} else {
|
||||
miter_len = 1.0 / d;
|
||||
if miter_len > 4.0 { miter_len = 4.0; } // Relaxed clamp for sharper corners
|
||||
normal = miter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let p = points[i];
|
||||
let nx = normal[0] * miter_len;
|
||||
let ny = normal[1] * miter_len;
|
||||
|
||||
point_pairs.push(super::common::RoadVertex {
|
||||
center: p,
|
||||
normal: [nx, ny],
|
||||
lanes,
|
||||
road_type,
|
||||
});
|
||||
point_pairs.push(super::common::RoadVertex {
|
||||
center: p,
|
||||
normal: [-nx, -ny],
|
||||
lanes,
|
||||
road_type,
|
||||
});
|
||||
}
|
||||
|
||||
// Triangulate
|
||||
let mut triangle_vertices = Vec::with_capacity((points.len() - 1) * 6);
|
||||
for i in 0..points.len()-1 {
|
||||
// Skip degenerate segment
|
||||
if dot(segment_normals[i], segment_normals[i]) == 0.0 { continue; }
|
||||
|
||||
// Indices into point_pairs
|
||||
let i_base = 2*i;
|
||||
let j_base = 2*(i+1);
|
||||
|
||||
let v1 = point_pairs[i_base]; // Left curr
|
||||
let v2 = point_pairs[i_base+1]; // Right curr
|
||||
let v3 = point_pairs[j_base]; // Left next
|
||||
let v4 = point_pairs[j_base+1]; // Right next
|
||||
|
||||
// Tri 1: v1, v2, v3
|
||||
triangle_vertices.push(v1);
|
||||
triangle_vertices.push(v2);
|
||||
triangle_vertices.push(v3);
|
||||
|
||||
// Tri 2: v2, v4, v3
|
||||
triangle_vertices.push(v2);
|
||||
triangle_vertices.push(v4);
|
||||
triangle_vertices.push(v3);
|
||||
}
|
||||
|
||||
triangle_vertices
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// MOTORWAY
|
||||
// ======================================================================================
|
||||
|
||||
pub fn create_road_motorway_outline_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>,
|
||||
theme: vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// Motorways: Outline
|
||||
let base_pixels = 4.0;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 4000.0);
|
||||
|
||||
let normal = model.normal;
|
||||
let offset = normal * width;
|
||||
let world_pos = model.center + offset;
|
||||
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);
|
||||
// Highlight miter/anti-alias edge data if needed
|
||||
out.v_normal = vec2<f32>(0.0, 0.0); // Placeholder
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Outline Color: Subtle Grey (Google Maps style)
|
||||
let color = mix(vec3<f32>(0.8, 0.8, 0.8), vec3<f32>(0.3, 0.3, 0.3), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Motorway Outline Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
pub fn create_road_motorway_pipeline(
|
||||
@@ -64,30 +188,95 @@ pub fn create_road_motorway_pipeline(
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let world_pos = model.position;
|
||||
// Motorways: Fill
|
||||
let base_pixels = 3.0;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 4000.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Light: #e990a0, Dark: #d97080 (slightly darker/richer)
|
||||
let color = mix(vec3<f32>(0.91, 0.56, 0.63), vec3<f32>(0.85, 0.44, 0.50), is_dark);
|
||||
// Fill Color: Slight off-white/warm #fffcfa
|
||||
let color = mix(vec3<f32>(1.0, 0.99, 0.98), vec3<f32>(0.5, 0.5, 0.5), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_simple_pipeline(device, format, bind_group_layout, &shader, "Motorway Pipeline", wgpu::PrimitiveTopology::LineList)
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Motorway Fill Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// PRIMARY
|
||||
// ======================================================================================
|
||||
|
||||
pub fn create_road_primary_outline_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>,
|
||||
theme: vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// Primary: Outline
|
||||
let base_pixels = 3.5;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 2000.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Outline: Subtle Grey
|
||||
let color = mix(vec3<f32>(0.8, 0.8, 0.8), vec3<f32>(0.3, 0.3, 0.3), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Primary Road Outline Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
pub fn create_road_primary_pipeline(
|
||||
@@ -105,30 +294,97 @@ pub fn create_road_primary_pipeline(
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let world_pos = model.position;
|
||||
// Primary: Fill
|
||||
let base_pixels = 2.5;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 2000.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Light: #fdbf6f, Dark: #e09f3f
|
||||
// Fill
|
||||
// Fill
|
||||
let color = mix(vec3<f32>(0.99, 0.75, 0.44), vec3<f32>(0.88, 0.62, 0.25), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_simple_pipeline(device, format, bind_group_layout, &shader, "Primary Road Pipeline", wgpu::PrimitiveTopology::LineList)
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Primary Road Fill Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// SECONDARY
|
||||
// ======================================================================================
|
||||
|
||||
pub fn create_road_secondary_outline_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>,
|
||||
theme: vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// Secondary: Outline
|
||||
let base_pixels = 4.0;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 500.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Outline: Gray
|
||||
// Outline: Gray
|
||||
let color = mix(vec3<f32>(0.5, 0.5, 0.5), vec3<f32>(0.2, 0.2, 0.2), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Secondary Road Outline Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
pub fn create_road_secondary_pipeline(
|
||||
@@ -146,30 +402,97 @@ pub fn create_road_secondary_pipeline(
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let world_pos = model.position;
|
||||
// Secondary: Fill
|
||||
let base_pixels = 3.0;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 500.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Light: White, Dark: #444444
|
||||
let color = mix(vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(0.27, 0.27, 0.27), is_dark);
|
||||
// Fill: White/Dark Gray
|
||||
// Fill: White/Dark Gray
|
||||
let color = mix(vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(0.35, 0.35, 0.35), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_simple_pipeline(device, format, bind_group_layout, &shader, "Secondary Road Pipeline", wgpu::PrimitiveTopology::LineList)
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Secondary Road Fill Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// RESIDENTIAL
|
||||
// ======================================================================================
|
||||
|
||||
pub fn create_road_residential_outline_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>,
|
||||
theme: vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// Residential: Outline
|
||||
let base_pixels = 3.2;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 250.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Outline: Gray
|
||||
// Outline: Gray
|
||||
let color = mix(vec3<f32>(0.5, 0.5, 0.5), vec3<f32>(0.2, 0.2, 0.2), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Residential Road Outline Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
pub fn create_road_residential_pipeline(
|
||||
@@ -187,28 +510,39 @@ pub fn create_road_residential_pipeline(
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) normal: vec2<f32>,
|
||||
@location(2) lanes: f32,
|
||||
@location(3) road_type: f32,
|
||||
};
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) v_normal: vec2<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let world_pos = model.position;
|
||||
// Residential: Fill
|
||||
let base_pixels = 2.2;
|
||||
let lane_factor = model.lanes / 2.0;
|
||||
let width = (base_pixels * lane_factor) / (camera.params.x * 250.0);
|
||||
|
||||
let world_pos = model.center + model.normal * width;
|
||||
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);
|
||||
out.v_normal = model.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Light: White, Dark: #333333
|
||||
let color = mix(vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(0.2, 0.2, 0.2), is_dark);
|
||||
// Fill: White/Dark Gray
|
||||
// Fill: White/Dark Gray
|
||||
let color = mix(vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(0.35, 0.35, 0.35), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
});
|
||||
create_simple_pipeline(device, format, bind_group_layout, &shader, "Residential Road Pipeline", wgpu::PrimitiveTopology::LineList)
|
||||
create_road_pipeline(device, format, bind_group_layout, &shader, "Residential Road Fill Pipeline", wgpu::PrimitiveTopology::TriangleList)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ pub fn create_water_pipeline(
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Water: Light: #9ecaff, Dark: #1a2639
|
||||
let is_dark = camera.theme.x;
|
||||
let color = mix(vec3<f32>(0.62, 0.79, 1.0), vec3<f32>(0.1, 0.15, 0.22), is_dark);
|
||||
let color = mix(vec3<f32>(0.647, 0.749, 0.867), vec3<f32>(0.1, 0.15, 0.22), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
@@ -99,7 +99,11 @@ pub fn create_water_pipeline(
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 4,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
@@ -137,7 +141,7 @@ pub fn create_water_line_pipeline(
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let is_dark = camera.theme.x;
|
||||
// Light: #a5bfdd (same/similar to water), Dark: #4a6fa5
|
||||
let color = mix(vec3<f32>(0.66, 0.82, 0.96), vec3<f32>(0.29, 0.44, 0.65), is_dark);
|
||||
let color = mix(vec3<f32>(0.647, 0.749, 0.867), vec3<f32>(0.29, 0.44, 0.65), is_dark);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
"#)),
|
||||
|
||||
Reference in New Issue
Block a user