update
This commit is contained in:
@@ -11,3 +11,6 @@ anyhow = "1.0"
|
||||
memmap2 = "0.9"
|
||||
dotenv = "0.15"
|
||||
earcutr = "0.4"
|
||||
wgpu = "0.19"
|
||||
bytemuck = { version = "1.14", features = ["derive"] }
|
||||
pollster = "0.3"
|
||||
|
||||
@@ -16,7 +16,8 @@ pub enum DbTask {
|
||||
table: &'static str,
|
||||
id: i64,
|
||||
tags: HashMap<String, String>,
|
||||
points: Vec<u8>,
|
||||
points: Vec<u8>,
|
||||
vertex_buffer: Vec<u8>,
|
||||
x: i32,
|
||||
y: i32
|
||||
},
|
||||
|
||||
@@ -38,6 +38,13 @@ async fn main() -> Result<()> {
|
||||
// Truncate tables
|
||||
scylla_repo.truncate_tables().await?;
|
||||
|
||||
// Initialize GPU mesh generation service
|
||||
println!("Initializing GPU mesh generation service...");
|
||||
let mesh_service = Arc::new(
|
||||
pollster::block_on(services::mesh_service::MeshGenerationService::new())?
|
||||
);
|
||||
println!("Mesh service initialized!");
|
||||
|
||||
let path = std::env::var("OSM_PBF_PATH")
|
||||
.or_else(|_| std::env::var("HOST_PBF_PATH"))
|
||||
.unwrap_or_else(|_| "europe-latest.osm.pbf".to_string());
|
||||
@@ -79,9 +86,9 @@ async fn main() -> Result<()> {
|
||||
let _ = repo.insert_node(zoom, id, lat, lon, tags, x, y).await;
|
||||
});
|
||||
}
|
||||
DbTask::Way { zoom, table, id, tags, points, x, y } => {
|
||||
DbTask::Way { zoom, table, id, tags, points, vertex_buffer, x, y } => {
|
||||
join_set.spawn(async move {
|
||||
let _ = repo.insert_way(table, zoom, id, tags, points, x, y).await;
|
||||
let _ = repo.insert_way(table, zoom, id, tags, points, vertex_buffer, x, y).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -95,8 +102,10 @@ async fn main() -> Result<()> {
|
||||
|
||||
// Run the PBF reader in a blocking task
|
||||
let tx_clone = tx.clone();
|
||||
let mesh_service_clone = mesh_service.clone();
|
||||
let reader_handle = tokio::task::spawn_blocking(move || -> Result<(usize, usize, usize)> {
|
||||
let tx = tx_clone;
|
||||
let mesh_svc = mesh_service_clone;
|
||||
let mut node_count = 0;
|
||||
let mut way_count = 0;
|
||||
let mut relation_count = 0;
|
||||
@@ -259,22 +268,140 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
if is_highway || treat_as_water_line {
|
||||
let task = DbTask::Way { zoom: zoom_i32, table: "ways", id, tags: tags.clone(), points: line_blob.clone(), x, y };
|
||||
// Generate road geometry
|
||||
let projected_points: Vec<[f32; 2]> = simplified_points.iter()
|
||||
.map(|(lat, lon)| {
|
||||
let (x, y) = GeometryService::project(*lat, *lon);
|
||||
[x, y]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let highway_tag = tags.get("highway").map(|s| s.as_str());
|
||||
let road_type = match highway_tag.unwrap_or("") {
|
||||
"motorway" | "motorway_link" | "trunk" | "trunk_link" => 0.0,
|
||||
"primary" | "primary_link" => 1.0,
|
||||
"secondary" | "secondary_link" => 2.0,
|
||||
_ => 3.0,
|
||||
};
|
||||
|
||||
let default_lanes: f32 = match highway_tag.unwrap_or("") {
|
||||
"motorway" | "trunk" => 4.0,
|
||||
"motorway_link" | "trunk_link" | "primary" => 2.0,
|
||||
_ => 2.0,
|
||||
};
|
||||
let lanes: f32 = tags.get("lanes")
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(default_lanes);
|
||||
|
||||
// DEBUG: Log first way to validate mesh generation
|
||||
if way_count == 1 {
|
||||
println!("DEBUG Way {}: {} projected points, generating mesh...", id, projected_points.len());
|
||||
}
|
||||
|
||||
let vertex_buffer = if treat_as_water_line {
|
||||
mesh_svc.generate_polygon_geometry(&projected_points)
|
||||
} else {
|
||||
mesh_svc.generate_road_geometry(&projected_points, lanes, road_type)
|
||||
};
|
||||
|
||||
// DEBUG: Log buffer size
|
||||
if way_count == 1 {
|
||||
println!("DEBUG Way {}: vertex_buffer size = {} bytes", id, vertex_buffer.len());
|
||||
}
|
||||
|
||||
let task = DbTask::Way {
|
||||
zoom: zoom_i32,
|
||||
table: "ways",
|
||||
id,
|
||||
tags: tags.clone(),
|
||||
points: line_blob.clone(),
|
||||
vertex_buffer,
|
||||
x,
|
||||
y
|
||||
};
|
||||
let _ = tx.blocking_send(task);
|
||||
}
|
||||
|
||||
if treat_as_building {
|
||||
let task = DbTask::Way { zoom: zoom_i32, table: "buildings", id, tags: tags.clone(), points: polygon_blob.clone(), x, y };
|
||||
// Generate building mesh
|
||||
let projected_points: Vec<[f32; 2]> = final_points.iter()
|
||||
.map(|(lat, lon)| {
|
||||
let (x, y) = GeometryService::project(*lat, *lon);
|
||||
[x, y]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let building_type = tags.get("building").map(|s| s.as_str()).unwrap_or("yes");
|
||||
let color: [f32; 3] = match building_type {
|
||||
"house" | "apartments" | "residential" | "detached" | "semidetached_house" | "terrace" | "dormitory" =>
|
||||
[0.95, 0.94, 0.91],
|
||||
"commercial" | "retail" | "office" | "supermarket" | "kiosk" | "hotel" =>
|
||||
[0.91, 0.89, 0.86],
|
||||
"industrial" | "warehouse" | "factory" | "manufacture" =>
|
||||
[0.85, 0.84, 0.80],
|
||||
_ => [0.85, 0.85, 0.85],
|
||||
};
|
||||
|
||||
let vertex_buffer = mesh_svc.generate_building_geometry(&projected_points, color);
|
||||
|
||||
let task = DbTask::Way {
|
||||
zoom: zoom_i32,
|
||||
table: "buildings",
|
||||
id,
|
||||
tags: tags.clone(),
|
||||
points: polygon_blob.clone(),
|
||||
vertex_buffer,
|
||||
x,
|
||||
y
|
||||
};
|
||||
let _ = tx.blocking_send(task);
|
||||
}
|
||||
|
||||
if treat_as_water_area {
|
||||
let task = DbTask::Way { zoom: zoom_i32, table: "water", id, tags: tags.clone(), points: polygon_blob.clone(), x, y };
|
||||
// Generate water polygon mesh
|
||||
let projected_points: Vec<[f32; 2]> = final_points.iter()
|
||||
.map(|(lat, lon)| {
|
||||
let (x, y) = GeometryService::project(*lat, *lon);
|
||||
[x, y]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let vertex_buffer = mesh_svc.generate_polygon_geometry(&projected_points);
|
||||
|
||||
let task = DbTask::Way {
|
||||
zoom: zoom_i32,
|
||||
table: "water",
|
||||
id,
|
||||
tags: tags.clone(),
|
||||
points: polygon_blob.clone(),
|
||||
vertex_buffer,
|
||||
x,
|
||||
y
|
||||
};
|
||||
let _ = tx.blocking_send(task);
|
||||
}
|
||||
|
||||
if treat_as_landuse {
|
||||
let task = DbTask::Way { zoom: zoom_i32, table: "landuse", id, tags: tags.clone(), points: polygon_blob.clone(), x, y };
|
||||
// Generate landuse polygon mesh
|
||||
let projected_points: Vec<[f32; 2]> = final_points.iter()
|
||||
.map(|(lat, lon)| {
|
||||
let (x, y) = GeometryService::project(*lat, *lon);
|
||||
[x, y]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let vertex_buffer = mesh_svc.generate_polygon_geometry(&projected_points);
|
||||
|
||||
let task = DbTask::Way {
|
||||
zoom: zoom_i32,
|
||||
table: "landuse",
|
||||
id,
|
||||
tags: tags.clone(),
|
||||
points: polygon_blob.clone(),
|
||||
vertex_buffer,
|
||||
x,
|
||||
y
|
||||
};
|
||||
let _ = tx.blocking_send(task);
|
||||
}
|
||||
|
||||
@@ -303,6 +430,20 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Also extract line ref (e.g., S1, U4) from route relations
|
||||
if let Some(line_ref) = tags.get("ref") {
|
||||
// Only propagate S-Bahn/U-Bahn style refs (starts with S or U followed by digit)
|
||||
if (line_ref.starts_with('S') || line_ref.starts_with('U')) && line_ref.len() >= 2 {
|
||||
let member_count = rel.members().filter(|m| matches!(m.member_type, osmpbf::RelMemberType::Way)).count();
|
||||
println!("DEBUG: Found transit line ref '{}' with {} way members", line_ref, member_count);
|
||||
for member in rel.members() {
|
||||
if let osmpbf::RelMemberType::Way = member.member_type {
|
||||
railway_store.set_ref(member.member_id, line_ref.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tags.get("type").map(|t| t == "multipolygon").unwrap_or(false) {
|
||||
let is_water = tags.get("natural").map(|v| v == "water" || v == "wetland" || v == "bay").unwrap_or(false) ||
|
||||
tags.get("waterway").map(|v| v == "riverbank" || v == "river" || v == "canal").unwrap_or(false) ||
|
||||
@@ -355,7 +496,27 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
let table = if is_water { "water" } else { "landuse" };
|
||||
let task = DbTask::Way { zoom: zoom_i32, table, id, tags: tags.clone(), points: polygon_blob.clone(), x, y };
|
||||
|
||||
// Generate polygon mesh for multipolygons
|
||||
let projected_points: Vec<[f32; 2]> = final_points.iter()
|
||||
.map(|(lat, lon)| {
|
||||
let (x, y) = GeometryService::project(*lat, *lon);
|
||||
[x, y]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let vertex_buffer = mesh_svc.generate_polygon_geometry(&projected_points);
|
||||
|
||||
let task = DbTask::Way {
|
||||
zoom: zoom_i32,
|
||||
table,
|
||||
id,
|
||||
tags: tags.clone(),
|
||||
points: polygon_blob.clone(),
|
||||
vertex_buffer,
|
||||
x,
|
||||
y
|
||||
};
|
||||
let _ = tx.blocking_send(task);
|
||||
}
|
||||
}
|
||||
@@ -372,14 +533,18 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
})?;
|
||||
|
||||
let (railways, colors) = railway_store.into_data();
|
||||
println!("Inserting {} railway ways with colors...", railways.len());
|
||||
let (railways, colors, refs) = railway_store.into_data();
|
||||
println!("Inserting {} railway ways with colors and line refs...", railways.len());
|
||||
for (id, railway) in railways {
|
||||
let mut tags = railway.tags;
|
||||
|
||||
if let Some(colour) = colors.get(&id) {
|
||||
tags.insert("colour".to_string(), colour.clone());
|
||||
}
|
||||
|
||||
if let Some(line_ref) = refs.get(&id) {
|
||||
tags.insert("line_ref".to_string(), line_ref.clone());
|
||||
}
|
||||
|
||||
// Insert for all applicable zoom levels
|
||||
for &zoom in &FilteringService::ZOOM_LEVELS {
|
||||
@@ -387,6 +552,41 @@ async fn main() -> Result<()> {
|
||||
|
||||
let (x, y) = TileService::lat_lon_to_tile(railway.first_lat, railway.first_lon, zoom);
|
||||
let zoom_i32 = zoom as i32;
|
||||
|
||||
// Parse geometry from blob and generate railway mesh
|
||||
let mut points: Vec<[f32; 2]> = Vec::new();
|
||||
for chunk in railway.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;
|
||||
let (x, y) = GeometryService::project(lat, lon);
|
||||
points.push([x, y]);
|
||||
}
|
||||
|
||||
// Parse color and rail type
|
||||
let color_str = tags.get("colour").or(tags.get("color"));
|
||||
let color = color_str
|
||||
.map(|c| {
|
||||
let c = c.trim_start_matches('#');
|
||||
if c.len() == 6 {
|
||||
let r = u8::from_str_radix(&c[0..2], 16).unwrap_or(0) as f32 / 255.0;
|
||||
let g = u8::from_str_radix(&c[2..4], 16).unwrap_or(0) as f32 / 255.0;
|
||||
let b = u8::from_str_radix(&c[4..6], 16).unwrap_or(0) as f32 / 255.0;
|
||||
[r, g, b]
|
||||
} else {
|
||||
[0.0, 0.0, 0.0]
|
||||
}
|
||||
})
|
||||
.unwrap_or([0.0, 0.0, 0.0]);
|
||||
|
||||
let rail_type_str = tags.get("railway").map(|s| s.as_str()).unwrap_or("rail");
|
||||
let rail_type: f32 = match rail_type_str {
|
||||
"subway" => 1.0,
|
||||
"tram" => 2.0,
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
let vertex_buffer = mesh_svc.generate_railway_geometry(&points, color, rail_type);
|
||||
|
||||
let task = DbTask::Way {
|
||||
zoom: zoom_i32,
|
||||
@@ -394,6 +594,7 @@ async fn main() -> Result<()> {
|
||||
id,
|
||||
tags: tags.clone(),
|
||||
points: railway.points.clone(),
|
||||
vertex_buffer,
|
||||
x,
|
||||
y
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ pub struct RailwayWay {
|
||||
pub struct RailwayStore {
|
||||
ways: HashMap<i64, RailwayWay>, // way_id -> railway data
|
||||
way_colors: HashMap<i64, String>, // way_id -> colour from route relation
|
||||
way_refs: HashMap<i64, String>, // way_id -> ref (line name like S1, U3) from route relation
|
||||
}
|
||||
|
||||
impl RailwayStore {
|
||||
@@ -19,6 +20,7 @@ impl RailwayStore {
|
||||
Self {
|
||||
ways: HashMap::new(),
|
||||
way_colors: HashMap::new(),
|
||||
way_refs: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +33,20 @@ impl RailwayStore {
|
||||
self.way_colors.entry(way_id).or_insert(color);
|
||||
}
|
||||
|
||||
pub fn set_ref(&mut self, way_id: i64, line_ref: String) {
|
||||
// Only set if not already set (first route relation wins)
|
||||
self.way_refs.entry(way_id).or_insert(line_ref);
|
||||
}
|
||||
|
||||
pub fn get_color(&self, way_id: i64) -> Option<&String> {
|
||||
self.way_colors.get(&way_id)
|
||||
}
|
||||
|
||||
pub fn get_ref(&self, way_id: i64) -> Option<&String> {
|
||||
self.way_refs.get(&way_id)
|
||||
}
|
||||
|
||||
pub fn into_data(self) -> (HashMap<i64, RailwayWay>, HashMap<i64, String>) {
|
||||
(self.ways, self.way_colors)
|
||||
pub fn into_data(self) -> (HashMap<i64, RailwayWay>, HashMap<i64, String>, HashMap<i64, String>) {
|
||||
(self.ways, self.way_colors, self.way_refs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use scylla::{Session, SessionBuilder};
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use tokio::task::JoinSet;
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct ScyllaRepository {
|
||||
@@ -33,19 +32,19 @@ impl ScyllaRepository {
|
||||
|
||||
// Create tables
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.nodes (zoom int, tile_x int, tile_y int, id bigint, lat double, lon double, tags map<text, text>, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.ways (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.buildings (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.water (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.landuse (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.railways (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.ways (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, vertex_buffer blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.buildings (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, vertex_buffer blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.water (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, vertex_buffer blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.landuse (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, vertex_buffer blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
session.query("CREATE TABLE IF NOT EXISTS map_data.railways (zoom int, tile_x int, tile_y int, id bigint, tags map<text, text>, points blob, vertex_buffer blob, PRIMARY KEY ((zoom, tile_x, tile_y), id))", &[]).await?;
|
||||
|
||||
// Prepare statements
|
||||
let insert_node = session.prepare("INSERT INTO map_data.nodes (zoom, tile_x, tile_y, id, lat, lon, tags) VALUES (?, ?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_ways = session.prepare("INSERT INTO map_data.ways (zoom, tile_x, tile_y, id, tags, points) VALUES (?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_buildings = session.prepare("INSERT INTO map_data.buildings (zoom, tile_x, tile_y, id, tags, points) VALUES (?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_water = session.prepare("INSERT INTO map_data.water (zoom, tile_x, tile_y, id, tags, points) VALUES (?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_landuse = session.prepare("INSERT INTO map_data.landuse (zoom, tile_x, tile_y, id, tags, points) VALUES (?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_railways = session.prepare("INSERT INTO map_data.railways (zoom, tile_x, tile_y, id, tags, points) VALUES (?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_ways = session.prepare("INSERT INTO map_data.ways (zoom, tile_x, tile_y, id, tags, points, vertex_buffer) VALUES (?, ?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_buildings = session.prepare("INSERT INTO map_data.buildings (zoom, tile_x, tile_y, id, tags, points, vertex_buffer) VALUES (?, ?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_water = session.prepare("INSERT INTO map_data.water (zoom, tile_x, tile_y, id, tags, points, vertex_buffer) VALUES (?, ?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_landuse = session.prepare("INSERT INTO map_data.landuse (zoom, tile_x, tile_y, id, tags, points, vertex_buffer) VALUES (?, ?, ?, ?, ?, ?, ?)").await?;
|
||||
let insert_railways = session.prepare("INSERT INTO map_data.railways (zoom, tile_x, tile_y, id, tags, points, vertex_buffer) VALUES (?, ?, ?, ?, ?, ?, ?)").await?;
|
||||
|
||||
Ok(Self {
|
||||
session,
|
||||
@@ -78,7 +77,7 @@ impl ScyllaRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_way(&self, table: &str, zoom: i32, id: i64, tags: HashMap<String, String>, points: Vec<u8>, x: i32, y: i32) -> Result<()> {
|
||||
pub async fn insert_way(&self, table: &str, zoom: i32, id: i64, tags: HashMap<String, String>, points: Vec<u8>, vertex_buffer: Vec<u8>, x: i32, y: i32) -> Result<()> {
|
||||
let statement = match table {
|
||||
"ways" => &self.insert_ways,
|
||||
"buildings" => &self.insert_buildings,
|
||||
@@ -90,7 +89,7 @@ impl ScyllaRepository {
|
||||
|
||||
self.session.execute(
|
||||
statement,
|
||||
(zoom, x, y, id, tags, points),
|
||||
(zoom, x, y, id, tags, points, vertex_buffer),
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl GeometryService {
|
||||
|
||||
if max_dist > epsilon {
|
||||
let mut left = Self::simplify_points(&points[..=index], epsilon);
|
||||
let mut right = Self::simplify_points(&points[index..], epsilon);
|
||||
let right = Self::simplify_points(&points[index..], epsilon);
|
||||
|
||||
// Remove duplicate point at split
|
||||
left.pop();
|
||||
@@ -88,4 +88,18 @@ impl GeometryService {
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Web Mercator Projection
|
||||
/// Returns (x, y) in range [0.0, 1.0] for the whole world
|
||||
pub fn project(lat: f64, lon: f64) -> (f32, f32) {
|
||||
let x = (lon + 180.0) / 360.0;
|
||||
let lat_rad = lat.to_radians();
|
||||
let y = (1.0 - (lat_rad.tan() + (1.0 / lat_rad.cos())).ln() / std::f64::consts::PI) / 2.0;
|
||||
|
||||
// Validate results - clamp to valid range and handle NaN/Infinity
|
||||
let x = if x.is_finite() { (x as f32).clamp(0.0, 1.0) } else { 0.5 };
|
||||
let y = if y.is_finite() { (y as f32).clamp(0.0, 1.0) } else { 0.5 };
|
||||
|
||||
(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
340
importer/src/services/mesh_service.rs
Normal file
340
importer/src/services/mesh_service.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
use wgpu;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use anyhow::Result;
|
||||
|
||||
/// Vertex format for road geometry (matches frontend RoadVertex)
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct RoadVertex {
|
||||
pub center: [f32; 2],
|
||||
pub normal: [f32; 2],
|
||||
pub lanes: f32,
|
||||
pub road_type: f32,
|
||||
}
|
||||
|
||||
/// Vertex format for colored buildings (matches frontend ColoredVertex)
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct ColoredVertex {
|
||||
pub position: [f32; 2],
|
||||
pub color: [f32; 3],
|
||||
pub _padding: f32, // Align to 16 bytes
|
||||
}
|
||||
|
||||
/// Vertex format for simple polygons (matches frontend Vertex)
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct SimpleVertex {
|
||||
pub position: [f32; 2],
|
||||
}
|
||||
|
||||
/// Vertex format for railways (matches frontend RailwayVertex)
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct RailwayVertex {
|
||||
pub center: [f32; 2],
|
||||
pub normal: [f32; 2],
|
||||
pub color: [f32; 3],
|
||||
pub rail_type: f32,
|
||||
}
|
||||
|
||||
/// GPU-based mesh generation service
|
||||
/// Uses compute shaders to precompute geometry on the server
|
||||
/// Falls back to CPU-only if GPU is not available
|
||||
pub struct MeshGenerationService {
|
||||
device: Option<wgpu::Device>,
|
||||
queue: Option<wgpu::Queue>,
|
||||
}
|
||||
|
||||
impl MeshGenerationService {
|
||||
/// Initialize GPU device for headless compute
|
||||
/// Falls back to CPU-only if GPU is unavailable
|
||||
pub async fn new() -> Result<Self> {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::all(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
})
|
||||
.await;
|
||||
|
||||
match adapter {
|
||||
Some(adapter) => {
|
||||
match adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: Some("Mesh Generation Device"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
},
|
||||
None,
|
||||
).await {
|
||||
Ok((device, queue)) => {
|
||||
println!("GPU initialized: {:?}", adapter.get_info());
|
||||
Ok(Self { device: Some(device), queue: Some(queue) })
|
||||
}
|
||||
Err(e) => {
|
||||
println!("GPU device request failed: {}, falling back to CPU-only mode", e);
|
||||
Ok(Self { device: None, queue: None })
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("No GPU adapter found, using CPU-only mesh generation");
|
||||
Ok(Self { device: None, queue: None })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate road geometry with miter joins (CPU implementation for now)
|
||||
/// Returns vertex buffer as raw bytes ready for storage
|
||||
pub fn generate_road_geometry(
|
||||
&self,
|
||||
points: &[[f32; 2]],
|
||||
lanes: f32,
|
||||
road_type: f32,
|
||||
) -> Vec<u8> {
|
||||
let vertices = Self::generate_road_mesh(points, lanes, road_type);
|
||||
bytemuck::cast_slice(&vertices).to_vec()
|
||||
}
|
||||
|
||||
/// Generate building geometry (already triangulated)
|
||||
pub fn generate_building_geometry(
|
||||
&self,
|
||||
points: &[[f32; 2]],
|
||||
color: [f32; 3],
|
||||
) -> Vec<u8> {
|
||||
let vertices: Vec<ColoredVertex> = points
|
||||
.iter()
|
||||
.map(|&position| ColoredVertex {
|
||||
position,
|
||||
color,
|
||||
_padding: 0.0,
|
||||
})
|
||||
.collect();
|
||||
bytemuck::cast_slice(&vertices).to_vec()
|
||||
}
|
||||
|
||||
/// Generate simple polygon geometry (landuse, water)
|
||||
pub fn generate_polygon_geometry(&self, points: &[[f32; 2]]) -> Vec<u8> {
|
||||
let vertices: Vec<SimpleVertex> = points
|
||||
.iter()
|
||||
.map(|&position| SimpleVertex { position })
|
||||
.collect();
|
||||
bytemuck::cast_slice(&vertices).to_vec()
|
||||
}
|
||||
|
||||
/// Generate railway geometry with color
|
||||
pub fn generate_railway_geometry(
|
||||
&self,
|
||||
points: &[[f32; 2]],
|
||||
color: [f32; 3],
|
||||
rail_type: f32,
|
||||
) -> Vec<u8> {
|
||||
let vertices = Self::generate_railway_mesh(points, color, rail_type);
|
||||
bytemuck::cast_slice(&vertices).to_vec()
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// CPU-based mesh generation (port from frontend)
|
||||
// TODO: Replace with GPU compute shaders for better performance
|
||||
// ========================================================================
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
fn generate_road_mesh(points: &[[f32; 2]], lanes: f32, road_type: f32) -> Vec<RoadVertex> {
|
||||
if points.len() < 2 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Compute 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];
|
||||
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]);
|
||||
} else {
|
||||
segment_normals.push([-dy / len, dx / len]);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate vertex pairs with miter joins
|
||||
let mut point_pairs = Vec::with_capacity(points.len() * 2);
|
||||
|
||||
for i in 0..points.len() {
|
||||
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 Self::dot(n1, n1) == 0.0 {
|
||||
normal = n2;
|
||||
} else if Self::dot(n2, n2) == 0.0 {
|
||||
normal = n1;
|
||||
} else {
|
||||
let sum = [n1[0] + n2[0], n1[1] + n2[1]];
|
||||
let miter = Self::normalize(sum);
|
||||
let d = Self::dot(miter, n1);
|
||||
|
||||
if d.abs() < 0.1 {
|
||||
normal = n1;
|
||||
} else {
|
||||
miter_len = 1.0 / d;
|
||||
if miter_len > 4.0 {
|
||||
miter_len = 4.0;
|
||||
}
|
||||
normal = miter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let p = points[i];
|
||||
let nx = normal[0] * miter_len;
|
||||
let ny = normal[1] * miter_len;
|
||||
|
||||
point_pairs.push(RoadVertex {
|
||||
center: p,
|
||||
normal: [nx, ny],
|
||||
lanes,
|
||||
road_type,
|
||||
});
|
||||
point_pairs.push(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 {
|
||||
if Self::dot(segment_normals[i], segment_normals[i]) == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let i_base = 2 * i;
|
||||
let j_base = 2 * (i + 1);
|
||||
|
||||
let v1 = point_pairs[i_base];
|
||||
let v2 = point_pairs[i_base + 1];
|
||||
let v3 = point_pairs[j_base];
|
||||
let v4 = point_pairs[j_base + 1];
|
||||
|
||||
triangle_vertices.push(v1);
|
||||
triangle_vertices.push(v2);
|
||||
triangle_vertices.push(v3);
|
||||
|
||||
triangle_vertices.push(v2);
|
||||
triangle_vertices.push(v4);
|
||||
triangle_vertices.push(v3);
|
||||
}
|
||||
|
||||
triangle_vertices
|
||||
}
|
||||
|
||||
fn generate_railway_mesh(
|
||||
points: &[[f32; 2]],
|
||||
color: [f32; 3],
|
||||
rail_type: f32,
|
||||
) -> Vec<RailwayVertex> {
|
||||
if points.len() < 2 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Similar to road mesh but for railways
|
||||
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];
|
||||
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]);
|
||||
} else {
|
||||
segment_normals.push([-dy / len, dx / len]);
|
||||
}
|
||||
}
|
||||
|
||||
let mut point_pairs = Vec::with_capacity(points.len() * 2);
|
||||
|
||||
for i in 0..points.len() {
|
||||
let normal = if i == 0 {
|
||||
segment_normals[0]
|
||||
} else if i == points.len() - 1 {
|
||||
segment_normals[i - 1]
|
||||
} else {
|
||||
let n1 = segment_normals[i - 1];
|
||||
let n2 = segment_normals[i];
|
||||
let sum = [n1[0] + n2[0], n1[1] + n2[1]];
|
||||
Self::normalize(sum)
|
||||
};
|
||||
|
||||
let p = points[i];
|
||||
|
||||
point_pairs.push(RailwayVertex {
|
||||
center: p,
|
||||
normal,
|
||||
color,
|
||||
rail_type,
|
||||
});
|
||||
point_pairs.push(RailwayVertex {
|
||||
center: p,
|
||||
normal: [-normal[0], -normal[1]],
|
||||
color,
|
||||
rail_type,
|
||||
});
|
||||
}
|
||||
|
||||
let mut triangle_vertices = Vec::with_capacity((points.len() - 1) * 6);
|
||||
for i in 0..points.len() - 1 {
|
||||
if Self::dot(segment_normals[i], segment_normals[i]) == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let i_base = 2 * i;
|
||||
let j_base = 2 * (i + 1);
|
||||
|
||||
let v1 = point_pairs[i_base];
|
||||
let v2 = point_pairs[i_base + 1];
|
||||
let v3 = point_pairs[j_base];
|
||||
let v4 = point_pairs[j_base + 1];
|
||||
|
||||
triangle_vertices.push(v1);
|
||||
triangle_vertices.push(v2);
|
||||
triangle_vertices.push(v3);
|
||||
|
||||
triangle_vertices.push(v2);
|
||||
triangle_vertices.push(v4);
|
||||
triangle_vertices.push(v3);
|
||||
}
|
||||
|
||||
triangle_vertices
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ pub mod multipolygon_service;
|
||||
pub mod filtering_service;
|
||||
pub mod tile_service;
|
||||
pub mod railway_service;
|
||||
pub mod mesh_service;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user