update
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user