udpate sick
This commit is contained in:
@@ -38,6 +38,7 @@ web-sys = { version = "0.3", features = [
|
||||
"DomTokenList",
|
||||
"CssStyleDeclaration",
|
||||
"Performance",
|
||||
"CanvasRenderingContext2d",
|
||||
] }
|
||||
wgpu = { version = "0.19", default-features = false, features = ["webgl", "wgsl"] }
|
||||
winit = { version = "0.29", default-features = false, features = ["rwh_06"] }
|
||||
|
||||
@@ -520,6 +520,18 @@
|
||||
<div class="compass-center"></div>
|
||||
</div>
|
||||
|
||||
<!-- Canvas for GPU-based label rendering (Apple Maps approach) -->
|
||||
<canvas id="label-canvas" style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
"></canvas>
|
||||
|
||||
<div id="labels"></div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -255,8 +255,8 @@ pub fn extract_labels(tile_data: &TileData) -> Vec<CachedLabel> {
|
||||
} else {
|
||||
"rail"
|
||||
};
|
||||
// Log for debugging
|
||||
web_sys::console::log_1(&format!("Transit label found: {} at ({}, {})", line_ref, mid_point[0], mid_point[1]).into());
|
||||
// Debug logging removed for production performance
|
||||
// web_sys::console::log_1(&format!("Transit label found: {} at ({}, {})", line_ref, mid_point[0], mid_point[1]).into());
|
||||
|
||||
candidates.push(CachedLabel {
|
||||
name: line_ref.to_string(),
|
||||
@@ -273,7 +273,7 @@ pub fn extract_labels(tile_data: &TileData) -> Vec<CachedLabel> {
|
||||
candidates
|
||||
}
|
||||
|
||||
/// Update DOM labels using cached data - much faster than processing raw data each frame
|
||||
/// Update labels using Canvas API (GPU-accelerated) - Apple Maps approach
|
||||
pub fn update_labels(
|
||||
window: &web_sys::Window,
|
||||
camera: &Camera,
|
||||
@@ -283,8 +283,45 @@ pub fn update_labels(
|
||||
_scale_factor: f64,
|
||||
) {
|
||||
let document = window.document().unwrap();
|
||||
let container = document.get_element_by_id("labels").unwrap();
|
||||
container.set_inner_html("");
|
||||
|
||||
// Get canvas and 2D context
|
||||
let canvas = document
|
||||
.get_element_by_id("label-canvas")
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.unwrap();
|
||||
|
||||
let ctx = canvas
|
||||
.get_context("2d")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
|
||||
// Handle high-DPI displays - only resize if dimensions changed
|
||||
let dpr = window.device_pixel_ratio();
|
||||
let canvas_width = (width * dpr) as u32;
|
||||
let canvas_height = (height * dpr) as u32;
|
||||
|
||||
// Only resize if dimensions changed to prevent flickering
|
||||
if canvas.width() != canvas_width || canvas.height() != canvas_height {
|
||||
canvas.set_width(canvas_width);
|
||||
canvas.set_height(canvas_height);
|
||||
|
||||
// Set CSS size
|
||||
let canvas_html: web_sys::HtmlElement = canvas.clone().dyn_into().unwrap();
|
||||
let style = canvas_html.style();
|
||||
let _ = style.set_property("width", &format!("{}px", width));
|
||||
let _ = style.set_property("height", &format!("{}px", height));
|
||||
}
|
||||
|
||||
// Reset transform and clear
|
||||
ctx.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0).unwrap();
|
||||
ctx.clear_rect(0.0, 0.0, canvas_width as f64, canvas_height as f64);
|
||||
|
||||
// Apply DPI scaling
|
||||
let _ = ctx.scale(dpr, dpr);
|
||||
|
||||
|
||||
let visible_tiles = TileService::get_visible_tiles(camera);
|
||||
let is_dark = document.document_element()
|
||||
@@ -307,7 +344,63 @@ pub fn update_labels(
|
||||
if label.label_type == LabelType::Transit && !show_transit {
|
||||
continue;
|
||||
}
|
||||
if (zoom as f64) > label.min_zoom {
|
||||
|
||||
// Zoom range filtering - each label type has min and max zoom
|
||||
let (min_zoom, max_zoom) = match label.label_type {
|
||||
LabelType::Country => (0.0, 1000.0),
|
||||
LabelType::City => {
|
||||
// Cities have different ranges based on their min_zoom (which indicates size)
|
||||
if label.min_zoom < 100.0 {
|
||||
// Large cities (city/capital)
|
||||
(20.0, 50000.0)
|
||||
} else if label.min_zoom < 1000.0 {
|
||||
// Towns
|
||||
(500.0, 100000.0)
|
||||
} else if label.min_zoom < 3000.0 {
|
||||
// Villages
|
||||
(2000.0, 200000.0)
|
||||
} else {
|
||||
// Hamlets/suburbs
|
||||
(4000.0, 300000.0)
|
||||
}
|
||||
},
|
||||
LabelType::Street => {
|
||||
// Streets should only show in a specific zoom range
|
||||
if label.min_zoom < 10000.0 {
|
||||
// Motorways/trunk
|
||||
(5000.0, 150000.0)
|
||||
} else if label.min_zoom < 30000.0 {
|
||||
// Primary
|
||||
(20000.0, 200000.0)
|
||||
} else if label.min_zoom < 80000.0 {
|
||||
// Secondary
|
||||
(50000.0, 300000.0)
|
||||
} else if label.min_zoom < 150000.0 {
|
||||
// Tertiary
|
||||
(100000.0, 500000.0)
|
||||
} else {
|
||||
// Residential
|
||||
(300000.0, 1000000.0)
|
||||
}
|
||||
},
|
||||
LabelType::Poi => {
|
||||
// POIs have different ranges based on importance
|
||||
if label.min_zoom < 1000.0 {
|
||||
// Major POIs (attractions, museums)
|
||||
(500.0, 100000.0)
|
||||
} else if label.min_zoom < 3000.0 {
|
||||
// Medium POIs (hotels, hospitals)
|
||||
(2000.0, 200000.0)
|
||||
} else {
|
||||
// Minor POIs (restaurants, cafes)
|
||||
(5000.0, 300000.0)
|
||||
}
|
||||
},
|
||||
LabelType::Transit => (100.0, 50000.0),
|
||||
};
|
||||
|
||||
let zoom_f64 = zoom as f64;
|
||||
if zoom_f64 >= min_zoom && zoom_f64 <= max_zoom {
|
||||
render_list.push(label);
|
||||
}
|
||||
}
|
||||
@@ -317,21 +410,22 @@ pub fn update_labels(
|
||||
// Sort by priority (high to low)
|
||||
render_list.sort_by(|a, b| b.priority.cmp(&a.priority));
|
||||
|
||||
// Zoom-based label limit to prevent DOM thrashing at low zoom levels
|
||||
// Zoom-based label limit to prevent performance issues
|
||||
let max_labels = if zoom < 100.0 {
|
||||
50 // World/continent view - only show major features
|
||||
20
|
||||
} else if zoom < 1000.0 {
|
||||
200 // Country/region view - show cities and major POIs
|
||||
100
|
||||
} else if zoom < 5000.0 {
|
||||
300
|
||||
} else {
|
||||
usize::MAX // City view and closer - show all labels
|
||||
500
|
||||
};
|
||||
|
||||
// Truncate to limit
|
||||
if render_list.len() > max_labels {
|
||||
render_list.truncate(max_labels);
|
||||
}
|
||||
|
||||
// Collision detection and placement
|
||||
// Collision detection
|
||||
let mut placed_rects: Vec<(f64, f64, f64, f64)> = Vec::new();
|
||||
|
||||
for label in render_list {
|
||||
@@ -340,20 +434,40 @@ pub fn update_labels(
|
||||
let cx = x * uniforms.params[0] + uniforms.params[2];
|
||||
let cy = y * uniforms.params[1] + uniforms.params[3];
|
||||
|
||||
// Tighter clip check - skip labels clearly off-screen
|
||||
// Clip check
|
||||
if cx < -1.0 || cx > 1.0 || cy < -1.0 || cy > 1.0 { continue; }
|
||||
|
||||
// Convert to CSS pixels
|
||||
let css_x = (cx as f64 + 1.0) * 0.5 * client_width;
|
||||
let css_y = (1.0 - cy as f64) * 0.5 * client_height;
|
||||
|
||||
// Estimate dimensions
|
||||
let (est_w, est_h) = match label.label_type {
|
||||
LabelType::Country => (label.name.len() as f64 * 12.0 + 20.0, 24.0),
|
||||
LabelType::City => (label.name.len() as f64 * 8.0 + 10.0, 16.0),
|
||||
LabelType::Street => (label.name.len() as f64 * 6.0 + 8.0, 12.0),
|
||||
LabelType::Poi => (label.name.len() as f64 * 6.5 + 10.0, 14.0),
|
||||
LabelType::Transit => (label.name.len() as f64 * 10.0 + 16.0, 20.0), // Badge style
|
||||
// Set font and style based on label type
|
||||
let (font, color, est_w, est_h) = match label.label_type {
|
||||
LabelType::Country => {
|
||||
let font = "bold 24px Inter, -apple-system, sans-serif";
|
||||
let color = if is_dark { "#ffffff" } else { "#000000" };
|
||||
(font, color, label.name.len() as f64 * 16.0 + 20.0, 32.0)
|
||||
}
|
||||
LabelType::City => {
|
||||
let font = "600 18px Inter, -apple-system, sans-serif";
|
||||
let color = if is_dark { "#ffffff" } else { "#000000" };
|
||||
(font, color, label.name.len() as f64 * 12.0 + 10.0, 24.0)
|
||||
}
|
||||
LabelType::Street => {
|
||||
let font = "500 14px Inter, -apple-system, sans-serif";
|
||||
let color = if is_dark { "#cccccc" } else { "#333333" };
|
||||
(font, color, label.name.len() as f64 * 8.0 + 8.0, 18.0)
|
||||
}
|
||||
LabelType::Poi => {
|
||||
let font = "500 15px Inter, -apple-system, sans-serif";
|
||||
let color = if is_dark { "#dddddd" } else { "#222222" };
|
||||
(font, color, label.name.len() as f64 * 9.0 + 10.0, 20.0)
|
||||
}
|
||||
LabelType::Transit => {
|
||||
let font = "bold 14px Inter, -apple-system, sans-serif";
|
||||
let color = "#ffffff";
|
||||
(font, color, label.name.len() as f64 * 12.0 + 16.0, 24.0)
|
||||
}
|
||||
};
|
||||
|
||||
let rect_x = css_x - est_w / 2.0;
|
||||
@@ -376,35 +490,86 @@ pub fn update_labels(
|
||||
|
||||
placed_rects.push((rect_x, rect_y, est_w, est_h));
|
||||
|
||||
// Create DOM element
|
||||
let div = document.create_element("div").unwrap();
|
||||
// Draw label on canvas
|
||||
ctx.set_font(font);
|
||||
ctx.set_text_align("center");
|
||||
ctx.set_text_baseline("middle");
|
||||
|
||||
let class_name = match label.label_type {
|
||||
LabelType::Country => "label label-country".to_string(),
|
||||
LabelType::City => "label label-city".to_string(),
|
||||
LabelType::Street => format!("label label-street label-street-{}", label.category),
|
||||
LabelType::Poi => format!("label label-poi label-poi-{}", label.category),
|
||||
LabelType::Transit => format!("label label-transit label-transit-{}", label.category),
|
||||
};
|
||||
// Handle rotation for street labels
|
||||
if label.label_type == LabelType::Street && label.rotation.abs() > 0.5 {
|
||||
ctx.save();
|
||||
let _ = ctx.translate(css_x, css_y);
|
||||
let _ = ctx.rotate(label.rotation.to_radians());
|
||||
|
||||
// Draw text shadow for readability - stronger shadow
|
||||
if is_dark {
|
||||
ctx.set_shadow_blur(4.0);
|
||||
ctx.set_shadow_color("rgba(0, 0, 0, 0.9)");
|
||||
} else {
|
||||
ctx.set_shadow_blur(3.0);
|
||||
ctx.set_shadow_color("rgba(255, 255, 255, 1.0)");
|
||||
}
|
||||
|
||||
ctx.set_fill_style(&color.into());
|
||||
let _ = ctx.fill_text(&label.name, 0.0, 0.0);
|
||||
ctx.restore();
|
||||
} else if label.label_type == LabelType::Transit {
|
||||
// Draw badge background for transit labels
|
||||
let badge_width = est_w;
|
||||
let badge_height = est_h;
|
||||
|
||||
let badge_color = match label.category.as_str() {
|
||||
"sbahn" => "#408335", // S-Bahn green
|
||||
"ubahn" => "#0065AE", // U-Bahn blue
|
||||
_ => "#666666",
|
||||
};
|
||||
|
||||
ctx.set_fill_style(&badge_color.into());
|
||||
|
||||
// Draw rounded rectangle for badge
|
||||
if label.category == "sbahn" {
|
||||
// Circle for S-Bahn
|
||||
ctx.begin_path();
|
||||
let _ = ctx.arc(css_x, css_y, 12.0, 0.0, std::f64::consts::PI * 2.0);
|
||||
let _ = ctx.fill();
|
||||
} else {
|
||||
// Rounded rectangle for U-Bahn
|
||||
let x = css_x - badge_width / 2.0;
|
||||
let y = css_y - badge_height / 2.0;
|
||||
let radius = 4.0;
|
||||
|
||||
ctx.begin_path();
|
||||
let _ = ctx.move_to(x + radius, y);
|
||||
let _ = ctx.line_to(x + badge_width - radius, y);
|
||||
let _ = ctx.quadratic_curve_to(x + badge_width, y, x + badge_width, y + radius);
|
||||
let _ = ctx.line_to(x + badge_width, y + badge_height - radius);
|
||||
let _ = ctx.quadratic_curve_to(x + badge_width, y + badge_height, x + badge_width - radius, y + badge_height);
|
||||
let _ = ctx.line_to(x + radius, y + badge_height);
|
||||
let _ = ctx.quadratic_curve_to(x, y + badge_height, x, y + badge_height - radius);
|
||||
let _ = ctx.line_to(x, y + radius);
|
||||
let _ = ctx.quadratic_curve_to(x, y, x + radius, y);
|
||||
let _ = ctx.close_path();
|
||||
let _ = ctx.fill();
|
||||
}
|
||||
|
||||
// Draw white text on badge
|
||||
ctx.set_fill_style(&color.into());
|
||||
let _ = ctx.fill_text(&label.name, css_x, css_y);
|
||||
} else {
|
||||
// Regular labels with stronger shadow
|
||||
if is_dark {
|
||||
ctx.set_shadow_blur(4.0);
|
||||
ctx.set_shadow_color("rgba(0, 0, 0, 0.9)");
|
||||
} else {
|
||||
ctx.set_shadow_blur(3.0);
|
||||
ctx.set_shadow_color("rgba(255, 255, 255, 1.0)");
|
||||
}
|
||||
|
||||
ctx.set_fill_style(&color.into());
|
||||
let _ = ctx.fill_text(&label.name, css_x, css_y);
|
||||
}
|
||||
|
||||
div.set_class_name(&class_name);
|
||||
div.set_text_content(Some(&label.name));
|
||||
|
||||
let div_html: web_sys::HtmlElement = div.dyn_into().unwrap();
|
||||
let style = div_html.style();
|
||||
style.set_property("left", &format!("{}px", css_x)).unwrap();
|
||||
style.set_property("top", &format!("{}px", css_y)).unwrap();
|
||||
|
||||
let transform = match label.label_type {
|
||||
LabelType::Poi => "translate(-50%, 10px)".to_string(),
|
||||
LabelType::Street if label.rotation.abs() > 0.5 =>
|
||||
format!("translate(-50%, -50%) rotate({}deg)", label.rotation),
|
||||
LabelType::Transit => "translate(-50%, -50%)".to_string(), // Centered badge
|
||||
_ => "translate(-50%, -50%)".to_string(),
|
||||
};
|
||||
|
||||
style.set_property("transform", &transform).unwrap();
|
||||
|
||||
container.append_child(&div_html).unwrap();
|
||||
// Reset shadow
|
||||
ctx.set_shadow_blur(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,6 +491,7 @@ pub async fn run() {
|
||||
rpass.set_bind_group(0, &camera_bind_group, &[]);
|
||||
for buffers in &tiles_to_render {
|
||||
if buffers.water_index_count > 0 {
|
||||
rpass.set_bind_group(1, &buffers.tile_bind_group, &[]);
|
||||
rpass.set_vertex_buffer(0, buffers.water_vertex_buffer.slice(..));
|
||||
rpass.draw(0..buffers.water_index_count, 0..1);
|
||||
}
|
||||
@@ -588,9 +589,8 @@ pub async fn run() {
|
||||
aspect: camera_guard.aspect
|
||||
};
|
||||
|
||||
// Update labels every frame to ensure they move smoothly with the map
|
||||
// Performance is maintained through zoom-based label limits in labels.rs
|
||||
last_label_camera = (camera_guard.x, camera_guard.y, camera_guard.zoom);
|
||||
// Update labels every frame for smooth dragging
|
||||
// Performance is maintained through reduced label limits (20/100/300/500)
|
||||
update_labels(
|
||||
&web_sys::window().unwrap(),
|
||||
&temp_camera,
|
||||
|
||||
@@ -5,7 +5,8 @@ use super::common::{Vertex, create_simple_pipeline};
|
||||
pub fn create_water_pipeline(
|
||||
device: &wgpu::Device,
|
||||
format: &wgpu::TextureFormat,
|
||||
bind_group_layout: &wgpu::BindGroupLayout
|
||||
camera_bind_group_layout: &wgpu::BindGroupLayout,
|
||||
tile_bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
@@ -17,8 +18,16 @@ pub fn create_water_pipeline(
|
||||
@group(0) @binding(0)
|
||||
var<uniform> camera: CameraUniform;
|
||||
|
||||
struct TileUniform {
|
||||
origin: vec2<f32>, // Tile origin in global 0..1 space
|
||||
scale: f32, // Tile size in global space (1.0 / 2^zoom)
|
||||
_padding: f32,
|
||||
};
|
||||
@group(1) @binding(0)
|
||||
var<uniform> tile: TileUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(0) position: vec2<f32>, // 0..1 relative to tile
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@@ -31,15 +40,12 @@ pub fn create_water_pipeline(
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let world_pos = model.position;
|
||||
// Transform from tile-relative (0..1) to global (0..1)
|
||||
let world_pos = tile.origin + model.position * tile.scale;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -62,7 +68,7 @@ pub fn create_water_pipeline(
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Water Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
bind_group_layouts: &[camera_bind_group_layout, tile_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
|
||||
@@ -42,13 +42,33 @@ pub struct RenderService {
|
||||
pub landuse_green_pipeline: wgpu::RenderPipeline,
|
||||
pub landuse_residential_pipeline: wgpu::RenderPipeline,
|
||||
pub sand_pipeline: wgpu::RenderPipeline,
|
||||
|
||||
// Layouts
|
||||
pub tile_bind_group_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
impl RenderService {
|
||||
pub fn new(device: &wgpu::Device, format: &wgpu::TextureFormat, camera_layout: &wgpu::BindGroupLayout) -> Self {
|
||||
// Create tile bind group layout for tile-relative coordinates
|
||||
let tile_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
],
|
||||
label: Some("tile_bind_group_layout"),
|
||||
});
|
||||
|
||||
Self {
|
||||
building_pipeline: create_colored_building_pipeline(device, format, camera_layout),
|
||||
water_pipeline: create_water_pipeline(device, format, camera_layout),
|
||||
water_pipeline: create_water_pipeline(device, format, camera_layout, &tile_bind_group_layout),
|
||||
water_line_pipeline: create_water_line_pipeline(device, format, camera_layout),
|
||||
railway_pipeline: create_railway_pipeline(device, format, camera_layout),
|
||||
motorway_outline: create_road_motorway_outline_pipeline(device, format, camera_layout),
|
||||
@@ -62,6 +82,7 @@ impl RenderService {
|
||||
landuse_green_pipeline: create_landuse_green_pipeline(device, format, camera_layout),
|
||||
landuse_residential_pipeline: create_landuse_residential_pipeline(device, format, camera_layout),
|
||||
sand_pipeline: create_sand_pipeline(device, format, camera_layout),
|
||||
tile_bind_group_layout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,70 +244,23 @@ impl RenderService {
|
||||
}
|
||||
}
|
||||
|
||||
// Process water
|
||||
// Process water using pre-computed vertex buffers (tile-relative coordinates)
|
||||
if let Some(water) = state.water.get(&tile) {
|
||||
for w in water {
|
||||
// Check if it's a waterway (line) or water body (polygon)
|
||||
let is_line = w.tags.contains_key("waterway");
|
||||
|
||||
let mut poly_points: Vec<(f64, f64)> = Vec::new();
|
||||
for chunk in w.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]));
|
||||
poly_points.push((lat as f64, lon as f64));
|
||||
// Use pre-computed vertex buffer from importer
|
||||
// The buffer contains tile-relative coordinates (0..1 within tile)
|
||||
if w.vertex_buffer.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_line {
|
||||
// Simple line strip, expand to thin quad strip?
|
||||
// Original implementation likely used line topology or simple expansion.
|
||||
// Let's assume line strip for now or thin quad.
|
||||
// Wait, previous water line pipeline probably handles lines?
|
||||
// But pipelines::roads::generate_road_geometry makes quads.
|
||||
// For now, let's treat as Triangles for consistency via triangulation?
|
||||
// No, lines cannot be triangulated by earcut.
|
||||
// I'll project and push as line vertices?
|
||||
// But topology is TriangleList for all?
|
||||
// render_service uses `water_line_pipeline`.
|
||||
// If pipeline is line list, then points are enough.
|
||||
// Let's check pipelines/water.rs? Unsure.
|
||||
// Assuming line list for now or reusing road geometry with fixed width.
|
||||
// The previous code had `create_water_line_pipeline`.
|
||||
// Let's project and create simple line segments?
|
||||
// Or reuse triangulate? No.
|
||||
// Let's generate a "thin road" using road geometry for consistency?
|
||||
let centers: Vec<[f32; 2]> = poly_points.iter().map(|&(lat, lon)| {
|
||||
let (x, y) = project(lat, lon);
|
||||
[x as f32, y as f32]
|
||||
}).collect();
|
||||
// Use road geometry for lines with fixed width
|
||||
let _geom = pipelines::roads::generate_road_geometry(¢ers, 3.0, 3.0); // 3m width
|
||||
// Cast RoadVertex to Vertex (drop normal/lanes)?
|
||||
// No, target is `Vec<Vertex>`.
|
||||
// RoadVertex has normal/lanes. Vertex only position.
|
||||
// I'll manually generate a quad strip using normals logic inline?
|
||||
// Or just use points?
|
||||
// Actually, if I use `generate_road_geometry` I get `RoadVertex`.
|
||||
// Let's assume water lines are just lines.
|
||||
// I will push points as lines if topology supports it.
|
||||
// But `create_buffer_init` usage is generic.
|
||||
// Let's use `generate_road_geometry` logic but simplify to Vertex.
|
||||
// Or better: Just generate a thin strip.
|
||||
// I'll use simple earcut (if polygon) or just thick line logic.
|
||||
// I will skip lines for now to avoid compilation error if types mismatch,
|
||||
// OR try to project and push.
|
||||
// Wait, `Vertex` has just position.
|
||||
// I'll assume TriangleList topology.
|
||||
// I'll use earcut for water polygons.
|
||||
} else {
|
||||
// Polygon
|
||||
let triangulated = GeometryService::triangulate_polygon(&poly_points);
|
||||
for (lat, lon) in triangulated {
|
||||
let (x, y) = project(lat, lon);
|
||||
water_vertex_data.push(Vertex {
|
||||
position: [x as f32, y as f32],
|
||||
});
|
||||
}
|
||||
// Vertex buffer format: [f32; 2] positions in tile-relative space
|
||||
for chunk in w.vertex_buffer.chunks(8) {
|
||||
if chunk.len() < 8 { break; }
|
||||
let x = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4]));
|
||||
let y = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4]));
|
||||
water_vertex_data.push(Vertex {
|
||||
position: [x, y], // Already in tile-relative coordinates
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,6 +361,64 @@ impl RenderService {
|
||||
|
||||
// Only insert buffers if we actually created something
|
||||
if created_buffers {
|
||||
// Calculate tile origin and scale for tile-relative coordinates
|
||||
let (z, x, y) = tile;
|
||||
let tile_count = 2_f32.powi(z);
|
||||
let tile_size = 1.0 / tile_count;
|
||||
let tile_origin_x = x as f32 * tile_size;
|
||||
let tile_origin_y = y as f32 * tile_size;
|
||||
|
||||
// Create tile uniform buffer
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct TileUniform {
|
||||
origin: [f32; 2],
|
||||
scale: f32,
|
||||
_padding: f32,
|
||||
}
|
||||
|
||||
let tile_uniform = TileUniform {
|
||||
origin: [tile_origin_x, tile_origin_y],
|
||||
scale: tile_size,
|
||||
_padding: 0.0,
|
||||
};
|
||||
|
||||
let tile_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Tile Uniform Buffer"),
|
||||
contents: bytemuck::cast_slice(&[tile_uniform]),
|
||||
usage: wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
// Get RenderService to access tile_bind_group_layout
|
||||
// We need to pass it from lib.rs or store it somewhere accessible
|
||||
// For now, we'll recreate it (not ideal but functional)
|
||||
let tile_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
],
|
||||
label: Some("tile_bind_group_layout"),
|
||||
});
|
||||
|
||||
let tile_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &tile_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: tile_uniform_buffer.as_entire_binding(),
|
||||
}
|
||||
],
|
||||
label: Some("Tile Bind Group"),
|
||||
});
|
||||
|
||||
state.buffers.insert(tile, std::sync::Arc::new(TileBuffers {
|
||||
road_motorway_vertex_buffer,
|
||||
road_motorway_vertex_count,
|
||||
@@ -410,6 +442,7 @@ impl RenderService {
|
||||
railway_vertex_count,
|
||||
water_line_vertex_buffer,
|
||||
water_line_vertex_count,
|
||||
tile_bind_group,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ pub struct TileBuffers {
|
||||
|
||||
pub water_line_vertex_buffer: wgpu::Buffer,
|
||||
pub water_line_vertex_count: u32,
|
||||
|
||||
// Tile-specific uniform bind group for relative coordinates
|
||||
pub tile_bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
/// Type of label for styling
|
||||
|
||||
Reference in New Issue
Block a user