359 lines
11 KiB
Rust
359 lines
11 KiB
Rust
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> {
|
|
let debug_mesh = std::env::var("DEBUG_MESH").is_ok();
|
|
|
|
if points.len() < 2 {
|
|
if debug_mesh {
|
|
println!("DEBUG MESH: Too few points ({})", points.len());
|
|
}
|
|
return Vec::new();
|
|
}
|
|
|
|
// Compute normals for each segment
|
|
let mut segment_normals = Vec::with_capacity(points.len() - 1);
|
|
let mut degenerate_count = 0;
|
|
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]);
|
|
degenerate_count += 1;
|
|
} else {
|
|
segment_normals.push([-dy / len, dx / len]);
|
|
}
|
|
}
|
|
|
|
if debug_mesh && degenerate_count > 0 {
|
|
println!("DEBUG MESH: {}/{} segments degenerate", degenerate_count, segment_normals.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);
|
|
let mut skipped = 0;
|
|
for i in 0..points.len() - 1 {
|
|
if Self::dot(segment_normals[i], segment_normals[i]) == 0.0 {
|
|
skipped += 1;
|
|
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);
|
|
}
|
|
|
|
if debug_mesh {
|
|
println!("DEBUG MESH: Generated {} vertices from {} points (skipped {} segments)",
|
|
triangle_vertices.len(), points.len(), skipped);
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|