This commit is contained in:
Dongho Kim
2025-12-19 02:24:05 +09:00
parent 1dcdce3ef1
commit 136723ca24
20 changed files with 1422 additions and 603 deletions

View 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
}
}