update
This commit is contained in:
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
target/
|
||||||
|
backend/target/
|
||||||
|
frontend/node_modules/
|
||||||
|
importer/target/
|
||||||
|
.git/
|
||||||
|
*.swp
|
||||||
|
.env
|
||||||
@@ -45,12 +45,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let session_arc = Arc::new(session);
|
let session_arc = Arc::new(session);
|
||||||
println!("Connected to ScyllaDB!");
|
println!("Connected to ScyllaDB!");
|
||||||
|
|
||||||
|
// Connect to Redis
|
||||||
// Connect to Redis
|
// Connect to Redis
|
||||||
println!("Connecting to Redis...");
|
println!("Connecting to Redis...");
|
||||||
|
println!("Verifying new build... REDIS_URI should be used.");
|
||||||
let redis_uri = std::env::var("REDIS_URI")
|
let redis_uri = std::env::var("REDIS_URI")
|
||||||
.unwrap_or_else(|_| "redis://redis:6379".to_string());
|
.unwrap_or_else(|_| "redis://redis:6379".to_string());
|
||||||
let redis_repo = Arc::new(RedisRepository::new(&redis_uri).await?);
|
println!("Using Redis URI: {}", redis_uri);
|
||||||
println!("Connected to Redis!");
|
|
||||||
|
let redis_repo = loop {
|
||||||
|
match RedisRepository::new(&redis_uri).await {
|
||||||
|
Ok(repo) => {
|
||||||
|
println!("Connected to Redis!");
|
||||||
|
break Arc::new(repo);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to connect to Redis: {}. Retrying in 5 seconds...", e);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Dependency Injection
|
// Dependency Injection
|
||||||
let node_repo = Arc::new(NodeRepository::new(session_arc.clone()));
|
let node_repo = Arc::new(NodeRepository::new(session_arc.clone()));
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ services:
|
|||||||
scylla:
|
scylla:
|
||||||
image: scylladb/scylla:latest
|
image: scylladb/scylla:latest
|
||||||
container_name: scylla
|
container_name: scylla
|
||||||
command: --smp 1 --memory 2G --overprovisioned 1 --api-address 0.0.0.0 --max-memory-for-unlimited-query-soft-limit 1073741824 --tombstone-warn-threshold 10000000
|
command: --smp 4 --memory 16G --overprovisioned 0 --api-address 0.0.0.0 --max-memory-for-unlimited-query-hard-limit 4294967296
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- scylla_data:/var/lib/scylla
|
- scylla-data:/var/lib/scylla
|
||||||
networks:
|
networks:
|
||||||
- maps-net
|
- maps-net
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: map-redis
|
container_name: map-redis
|
||||||
command: redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru
|
command: redis-server --maxmemory 5gb --maxmemory-policy allkeys-lru
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
networks:
|
networks:
|
||||||
@@ -23,16 +24,28 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
target: backend
|
target: backend
|
||||||
container_name: map-app
|
container_name: map-app
|
||||||
ports:
|
networks:
|
||||||
- "3000:3000"
|
- proxy
|
||||||
|
- maps-net
|
||||||
|
environment:
|
||||||
|
- REDIS_URI=redis://map-redis:6379
|
||||||
depends_on:
|
depends_on:
|
||||||
- scylla
|
- scylla
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
- REDIS_URI=redis://redis:6379
|
|
||||||
networks:
|
|
||||||
- maps-net
|
|
||||||
restart: always
|
restart: always
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network=proxy
|
||||||
|
- traefik.http.routers.maps.entrypoints=http
|
||||||
|
- traefik.http.routers.maps.rule=Host(`maps.ekstrah.com`)
|
||||||
|
- traefik.http.middlewares.maps-redirect.redirectscheme.permanent=true
|
||||||
|
- traefik.http.middlewares.maps-redirect.redirectscheme.scheme=https
|
||||||
|
- traefik.http.routers.maps.middlewares=maps-redirect
|
||||||
|
- traefik.http.routers.maps-secure.entrypoints=https
|
||||||
|
- traefik.http.routers.maps-secure.rule=Host(`maps.ekstrah.com`)
|
||||||
|
- traefik.http.routers.maps-secure.tls=true
|
||||||
|
- traefik.http.routers.maps-secure.tls.certresolver=cloudflare
|
||||||
|
- traefik.http.routers.maps-secure.service=maps-secure-service
|
||||||
|
- traefik.http.services.maps-secure-service.loadbalancer.server.port=3000
|
||||||
|
|
||||||
importer:
|
importer:
|
||||||
build:
|
build:
|
||||||
@@ -42,23 +55,41 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${HOST_PBF_PATH:-./europe-latest.osm.pbf}:/app/data.osm.pbf
|
- ${HOST_PBF_PATH:-./europe-latest.osm.pbf}:/app/data.osm.pbf
|
||||||
- ${HOST_CACHE_DIR:-./cache}:/cache
|
- ${HOST_CACHE_DIR:-./cache}:/cache
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: 1
|
||||||
|
capabilities:
|
||||||
|
- gpu
|
||||||
|
- compute
|
||||||
|
- utility
|
||||||
environment:
|
environment:
|
||||||
- SCYLLA_URI=scylla:9042
|
- SCYLLA_URI=scylla:9042
|
||||||
- OSM_PBF_PATH=/app/data.osm.pbf
|
- OSM_PBF_PATH=/app/data.osm.pbf
|
||||||
- CACHE_DIR=/cache
|
- CACHE_DIR=/cache
|
||||||
- DEBUG_WAY_ID=99
|
- VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json
|
||||||
- VERBOSE_DEBUG=1
|
- NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
- NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics
|
||||||
|
# - DEBUG_WAY_ID=99 # Logs detailed info for specific way
|
||||||
|
# - VERBOSE_DEBUG=1 # Logs transit line relations
|
||||||
|
# - DEBUG_MESH=1 # Logs mesh generation and vertex buffer info
|
||||||
depends_on:
|
depends_on:
|
||||||
- scylla
|
- scylla
|
||||||
networks:
|
|
||||||
- maps-net
|
|
||||||
profiles:
|
profiles:
|
||||||
- import
|
- import
|
||||||
|
networks:
|
||||||
|
- maps-net
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
maps-net:
|
maps-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
scylla_data:
|
scylla-data:
|
||||||
|
driver: local
|
||||||
redis_data:
|
redis_data:
|
||||||
|
|
||||||
|
|||||||
@@ -51,3 +51,9 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
earcutr = "0.4"
|
earcutr = "0.4"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
opt-level = 3
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "abort"
|
||||||
|
|||||||
@@ -510,6 +510,68 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="loading-screen">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<div class="loading-text">Loading Maps...</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
#loading-screen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading-screen.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: #007AFF;
|
||||||
|
animation: spin 1s ease-in-out infinite;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] #loading-screen {
|
||||||
|
background-color: #f5f5f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .loading-text {
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .spinner {
|
||||||
|
border-color: rgba(0, 0, 0, 0.1);
|
||||||
|
border-top-color: #007AFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div id="user-location"></div>
|
<div id="user-location"></div>
|
||||||
<div id="compass">
|
<div id="compass">
|
||||||
<div class="direction n">N</div>
|
<div class="direction n">N</div>
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ pub fn project(lat: f64, lon: f64) -> (f32, f32) {
|
|||||||
(x, y)
|
(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// High precision Web Mercator Projection
|
||||||
|
/// Returns (x, y) in range [0.0, 1.0] for the whole world as f64
|
||||||
|
pub fn project_high_precision(lat: f64, lon: f64) -> (f64, f64) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
let x = if x.is_finite() { x.clamp(0.0, 1.0) } else { 0.5 };
|
||||||
|
let y = if y.is_finite() { y.clamp(0.0, 1.0) } else { 0.5 };
|
||||||
|
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
/// Kalman filter for smoothing GPS location updates
|
/// Kalman filter for smoothing GPS location updates
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct KalmanFilter {
|
pub struct KalmanFilter {
|
||||||
|
|||||||
@@ -222,7 +222,9 @@ pub fn extract_labels(tile_data: &TileData) -> Vec<CachedLabel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Process Railways (Transit line labels like S1, U3, etc.)
|
// 3. Process Railways (Transit line labels like S1, U3, etc.) - Grouped to reduce clutter
|
||||||
|
let mut transit_groups: HashMap<String, Vec<&crate::types::MapWay>> = HashMap::new();
|
||||||
|
|
||||||
for railway in &tile_data.railways {
|
for railway in &tile_data.railways {
|
||||||
// Get line reference (e.g., "S1", "U3", "S8") - stored as 'line_ref' by importer
|
// Get line reference (e.g., "S1", "U3", "S8") - stored as 'line_ref' by importer
|
||||||
let line_ref = railway.tags.get("line_ref").map(|s| s.as_str());
|
let line_ref = railway.tags.get("line_ref").map(|s| s.as_str());
|
||||||
@@ -232,42 +234,53 @@ pub fn extract_labels(tile_data: &TileData) -> Vec<CachedLabel> {
|
|||||||
if railway_type == Some("tram") { continue; }
|
if railway_type == Some("tram") { continue; }
|
||||||
let Some(line_ref) = line_ref else { continue; };
|
let Some(line_ref) = line_ref else { continue; };
|
||||||
if line_ref.is_empty() { continue; }
|
if line_ref.is_empty() { continue; }
|
||||||
|
|
||||||
// Parse points to find midpoint
|
transit_groups.entry(line_ref.to_string())
|
||||||
let mut parsed_points: Vec<[f64; 2]> = Vec::new();
|
.or_insert_with(Vec::new)
|
||||||
for chunk in railway.points.chunks(8) {
|
.push(railway);
|
||||||
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;
|
// Process each transit line group and pick the best segment(s) for labeling
|
||||||
parsed_points.push([lat, lon]);
|
for (line_ref, segments) in transit_groups {
|
||||||
|
// Sort segments by length (number of points as proxy) descending
|
||||||
|
// We only label the top 1 longest segment per tile to avoid spam
|
||||||
|
let best_segment = segments.iter().max_by_key(|r| r.points.len());
|
||||||
|
|
||||||
|
if let Some(railway) = best_segment {
|
||||||
|
// Parse points to find midpoint
|
||||||
|
let mut parsed_points: Vec<[f64; 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;
|
||||||
|
parsed_points.push([lat, lon]);
|
||||||
|
}
|
||||||
|
if parsed_points.len() < 2 { continue; }
|
||||||
|
|
||||||
|
// Use midpoint for label placement
|
||||||
|
let mid_idx = parsed_points.len() / 2;
|
||||||
|
let mid_point = parsed_points[mid_idx];
|
||||||
|
|
||||||
|
// Determine category based on line prefix
|
||||||
|
let category = if line_ref.starts_with('S') {
|
||||||
|
"sbahn"
|
||||||
|
} else if line_ref.starts_with('U') {
|
||||||
|
"ubahn"
|
||||||
|
} else {
|
||||||
|
"rail"
|
||||||
|
};
|
||||||
|
|
||||||
|
candidates.push(CachedLabel {
|
||||||
|
name: line_ref.to_string(),
|
||||||
|
lat: mid_point[0],
|
||||||
|
lon: mid_point[1],
|
||||||
|
label_type: LabelType::Transit,
|
||||||
|
rotation: 0.0, // Transit labels are always horizontal
|
||||||
|
priority: 150, // Very high priority - above cities
|
||||||
|
min_zoom: 100.0, // Show at most zoom levels
|
||||||
|
category: category.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if parsed_points.len() < 2 { continue; }
|
|
||||||
|
|
||||||
// Use midpoint for label placement
|
|
||||||
let mid_idx = parsed_points.len() / 2;
|
|
||||||
let mid_point = parsed_points[mid_idx];
|
|
||||||
|
|
||||||
// Determine category based on line prefix
|
|
||||||
let category = if line_ref.starts_with('S') {
|
|
||||||
"sbahn"
|
|
||||||
} else if line_ref.starts_with('U') {
|
|
||||||
"ubahn"
|
|
||||||
} else {
|
|
||||||
"rail"
|
|
||||||
};
|
|
||||||
// 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(),
|
|
||||||
lat: mid_point[0],
|
|
||||||
lon: mid_point[1],
|
|
||||||
label_type: LabelType::Transit,
|
|
||||||
rotation: 0.0, // Transit labels are always horizontal
|
|
||||||
priority: 150, // Very high priority - above cities
|
|
||||||
min_zoom: 100.0, // Show at most zoom levels
|
|
||||||
category: category.to_string(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates
|
candidates
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use winit::{
|
|||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
platform::web::WindowExtWebSys,
|
platform::web::WindowExtWebSys,
|
||||||
};
|
};
|
||||||
|
use web_sys::Window; // Ensure web_sys Window is available if needed, though usually covered by wasm_bindgen
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use crate::domain::camera::Camera;
|
use crate::domain::camera::Camera;
|
||||||
@@ -291,6 +292,20 @@ pub async fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide loading screen
|
||||||
|
if let Some(loader) = window_doc.get_element_by_id("loading-screen") {
|
||||||
|
let _ = loader.class_list().add_1("fade-out");
|
||||||
|
// Remove after transition
|
||||||
|
let closure = wasm_bindgen::closure::Closure::<dyn FnMut()>::new(move || {
|
||||||
|
let _ = loader.set_attribute("style", "display: none;");
|
||||||
|
});
|
||||||
|
window_doc.default_view().unwrap().set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||||
|
closure.as_ref().unchecked_ref(),
|
||||||
|
500,
|
||||||
|
).unwrap();
|
||||||
|
closure.forget();
|
||||||
|
}
|
||||||
|
|
||||||
// Event Loop
|
// Event Loop
|
||||||
event_loop.run(move |event, elwt| {
|
event_loop.run(move |event, elwt| {
|
||||||
elwt.set_control_flow(winit::event_loop::ControlFlow::Wait);
|
elwt.set_control_flow(winit::event_loop::ControlFlow::Wait);
|
||||||
@@ -381,7 +396,7 @@ pub async fn run() {
|
|||||||
for tile in tiles_to_process {
|
for tile in tiles_to_process {
|
||||||
// Call RenderService static helper? Or just logic.
|
// Call RenderService static helper? Or just logic.
|
||||||
// I put logic in RenderService::create_tile_buffers which takes state.
|
// I put logic in RenderService::create_tile_buffers which takes state.
|
||||||
RenderService::create_tile_buffers(&device, &mut state_guard, tile);
|
RenderService::create_tile_buffers(&device, &mut state_guard, tile, &render_service.tile_bind_group_layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,6 +582,7 @@ pub async fn run() {
|
|||||||
rpass.set_bind_group(0, &camera_bind_group, &[]);
|
rpass.set_bind_group(0, &camera_bind_group, &[]);
|
||||||
for buffers in &tiles_to_render {
|
for buffers in &tiles_to_render {
|
||||||
if buffers.railway_vertex_count > 0 {
|
if buffers.railway_vertex_count > 0 {
|
||||||
|
rpass.set_bind_group(1, &buffers.tile_bind_group, &[]);
|
||||||
rpass.set_vertex_buffer(0, buffers.railway_vertex_buffer.slice(..));
|
rpass.set_vertex_buffer(0, buffers.railway_vertex_buffer.slice(..));
|
||||||
rpass.draw(0..buffers.railway_vertex_count, 0..1);
|
rpass.draw(0..buffers.railway_vertex_count, 0..1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ fn dot(a: [f32; 2], b: [f32; 2]) -> f32 {
|
|||||||
|
|
||||||
/// Generate thick railway geometry (quads)
|
/// Generate thick railway geometry (quads)
|
||||||
pub fn generate_railway_geometry(points: &[[f32; 2]], color: [f32; 3], railway_type: f32) -> Vec<RailwayVertex> {
|
pub fn generate_railway_geometry(points: &[[f32; 2]], color: [f32; 3], railway_type: f32) -> Vec<RailwayVertex> {
|
||||||
let vertices = Vec::new();
|
if points.len() < 2 { return Vec::new(); }
|
||||||
if points.len() < 2 { return vertices; }
|
|
||||||
|
|
||||||
// Computes normals for each segment
|
// Computes normals for each segment
|
||||||
let mut segment_normals = Vec::with_capacity(points.len() - 1);
|
let mut segment_normals = Vec::with_capacity(points.len() - 1);
|
||||||
@@ -25,7 +24,7 @@ pub fn generate_railway_geometry(points: &[[f32; 2]], color: [f32; 3], railway_t
|
|||||||
let dx = p2[0] - p1[0];
|
let dx = p2[0] - p1[0];
|
||||||
let dy = p2[1] - p1[1];
|
let dy = p2[1] - p1[1];
|
||||||
let len = (dx*dx + dy*dy).sqrt();
|
let len = (dx*dx + dy*dy).sqrt();
|
||||||
if len < 0.000001 {
|
if len < 0.000000001 {
|
||||||
segment_normals.push([0.0, 0.0]); // Degenerate
|
segment_normals.push([0.0, 0.0]); // Degenerate
|
||||||
} else {
|
} else {
|
||||||
segment_normals.push([-dy/len, dx/len]);
|
segment_normals.push([-dy/len, dx/len]);
|
||||||
@@ -84,7 +83,7 @@ pub fn generate_railway_geometry(points: &[[f32; 2]], color: [f32; 3], railway_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Triangulate
|
// Triangulate
|
||||||
let mut triangle_vertices = Vec::with_capacity((points.len() - 1) * 6);
|
let mut triangle_vertices: Vec<RailwayVertex> = Vec::with_capacity((points.len() - 1) * 6);
|
||||||
for i in 0..points.len()-1 {
|
for i in 0..points.len()-1 {
|
||||||
// Skip degenerate segment
|
// Skip degenerate segment
|
||||||
if dot(segment_normals[i], segment_normals[i]) == 0.0 { continue; }
|
if dot(segment_normals[i], segment_normals[i]) == 0.0 { continue; }
|
||||||
@@ -107,14 +106,78 @@ pub fn generate_railway_geometry(points: &[[f32; 2]], color: [f32; 3], railway_t
|
|||||||
triangle_vertices.push(v4);
|
triangle_vertices.push(v4);
|
||||||
triangle_vertices.push(v3);
|
triangle_vertices.push(v3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start Cap (at index 0)
|
||||||
|
if points.len() >= 2 {
|
||||||
|
let p0 = points[0];
|
||||||
|
let p1 = points[1];
|
||||||
|
let tangent = normalize([p1[0]-p0[0], p1[1]-p0[1]]);
|
||||||
|
let neg_tangent = [-tangent[0], -tangent[1]];
|
||||||
|
|
||||||
|
let v_left = point_pairs[0];
|
||||||
|
let v_right = point_pairs[1];
|
||||||
|
|
||||||
|
let cap_tris: Vec<RailwayVertex> = generate_railway_caps(p0, v_left, v_right, neg_tangent);
|
||||||
|
triangle_vertices.extend(cap_tris);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End Cap (at index len-1)
|
||||||
|
if points.len() >= 2 {
|
||||||
|
let last = points.len() - 1;
|
||||||
|
let p_last = points[last];
|
||||||
|
let p_prev = points[last-1];
|
||||||
|
let tangent = normalize([p_last[0]-p_prev[0], p_last[1]-p_prev[1]]);
|
||||||
|
|
||||||
|
let v_left = point_pairs[last*2];
|
||||||
|
let v_right = point_pairs[last*2 + 1];
|
||||||
|
|
||||||
|
let cap_tris: Vec<RailwayVertex> = generate_railway_caps(p_last, v_left, v_right, tangent);
|
||||||
|
triangle_vertices.extend(cap_tris);
|
||||||
|
}
|
||||||
|
|
||||||
triangle_vertices
|
triangle_vertices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_railway_caps(
|
||||||
|
p: [f32; 2],
|
||||||
|
v_left: RailwayVertex,
|
||||||
|
v_right: RailwayVertex,
|
||||||
|
tangent_direction: [f32; 2] // Direction to extrude (outwards from line)
|
||||||
|
) -> Vec<RailwayVertex> {
|
||||||
|
// Generate two new vertices that are extruded "outwards" along the tangent
|
||||||
|
// Vertex shader uses: pos = center + normal * width
|
||||||
|
// We want: pos = center + (normal_component + tangent_component) * width
|
||||||
|
// So we just add the tangent to the normal vector of the existing vertices.
|
||||||
|
|
||||||
|
// Left Cap Vertex
|
||||||
|
let v_cap_left = RailwayVertex {
|
||||||
|
center: p,
|
||||||
|
normal: [v_left.normal[0] + tangent_direction[0], v_left.normal[1] + tangent_direction[1]],
|
||||||
|
..v_left
|
||||||
|
};
|
||||||
|
|
||||||
|
// Right Cap Vertex
|
||||||
|
let v_cap_right = RailwayVertex {
|
||||||
|
center: p,
|
||||||
|
normal: [v_right.normal[0] + tangent_direction[0], v_right.normal[1] + tangent_direction[1]],
|
||||||
|
..v_right
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return as a quad strip sequence (v_left, v_cap_left, v_cap_right, etc for triangulation)
|
||||||
|
// Actually method above expects triangles.
|
||||||
|
// T1: v_left, v_cap_left, v_cap_right
|
||||||
|
// T2: v_left, v_cap_right, v_right
|
||||||
|
vec![
|
||||||
|
v_left, v_cap_left, v_cap_right,
|
||||||
|
v_left, v_cap_right, v_right
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_railway_pipeline(
|
pub fn create_railway_pipeline(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
format: &wgpu::TextureFormat,
|
format: &wgpu::TextureFormat,
|
||||||
bind_group_layout: &wgpu::BindGroupLayout
|
bind_group_layout: &wgpu::BindGroupLayout,
|
||||||
|
tile_bind_group_layout: &wgpu::BindGroupLayout
|
||||||
) -> wgpu::RenderPipeline {
|
) -> wgpu::RenderPipeline {
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
@@ -125,6 +188,13 @@ pub fn create_railway_pipeline(
|
|||||||
};
|
};
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var<uniform> camera: CameraUniform;
|
var<uniform> camera: CameraUniform;
|
||||||
|
|
||||||
|
struct TileUniform {
|
||||||
|
origin: vec2<f32>,
|
||||||
|
scale: f32,
|
||||||
|
};
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<uniform> tile: TileUniform;
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) center: vec2<f32>,
|
@location(0) center: vec2<f32>,
|
||||||
@@ -153,11 +223,20 @@ pub fn create_railway_pipeline(
|
|||||||
base_pixels = 2.0; // U-Bahn - thinner
|
base_pixels = 2.0; // U-Bahn - thinner
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using 1000.0 constant to make lines thicker (visible on standard screens)
|
// Calculate line width in WORLD coordinates
|
||||||
|
// camera.params.x = zoom / aspect (World -> Clip X scale)
|
||||||
|
// We want: width_world * scale_x = width_clip
|
||||||
|
// width_clip = pixel_width / screen_width * 2.0
|
||||||
|
// For now, continuing with previous scale factor logic approx:
|
||||||
let width = base_pixels / (camera.params.x * 1000.0);
|
let width = base_pixels / (camera.params.x * 1000.0);
|
||||||
|
|
||||||
let offset = model.normal * width;
|
let offset = model.normal * width;
|
||||||
let world_pos = model.center + offset;
|
|
||||||
|
// Tile-Relative to World Coordinate Transformation
|
||||||
|
// model.center is 0.0-1.0 relative to tile
|
||||||
|
// tile.origin is top-left of tile in world coords
|
||||||
|
// tile.scale is size of tile in world coords
|
||||||
|
let world_pos = tile.origin + (model.center * tile.scale) + offset;
|
||||||
|
|
||||||
let x = world_pos.x * camera.params.x + camera.params.z;
|
let x = world_pos.x * camera.params.x + camera.params.z;
|
||||||
let y = world_pos.y * camera.params.y + camera.params.w;
|
let y = world_pos.y * camera.params.y + camera.params.w;
|
||||||
@@ -193,12 +272,12 @@ pub fn create_railway_pipeline(
|
|||||||
// When transit mode is OFF: Dim/hide railways
|
// When transit mode is OFF: Dim/hide railways
|
||||||
if (!is_transit_mode) {
|
if (!is_transit_mode) {
|
||||||
// Transit mode is OFF - hide all railways
|
// Transit mode is OFF - hide all railways
|
||||||
return vec4<f32>(final_color, 0.0);
|
discard;
|
||||||
} else {
|
} else {
|
||||||
// Transit mode is ON - show railways
|
// Transit mode is ON - show railways
|
||||||
// Dim non-colored railways slightly to emphasize colored ones
|
// Dim non-colored railways slightly to emphasize colored ones
|
||||||
if (!has_color) {
|
if (!has_color) {
|
||||||
return vec4<f32>(mix(final_color, vec3<f32>(0.5, 0.5, 0.5), 0.3), 0.5);
|
return vec4<f32>(mix(final_color, vec3<f32>(0.5, 0.5, 0.5), 0.3), 1.0);
|
||||||
}
|
}
|
||||||
return vec4<f32>(final_color, 1.0);
|
return vec4<f32>(final_color, 1.0);
|
||||||
}
|
}
|
||||||
@@ -208,7 +287,7 @@ pub fn create_railway_pipeline(
|
|||||||
|
|
||||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("Railway Pipeline Layout"),
|
label: Some("Railway Pipeline Layout"),
|
||||||
bind_group_layouts: &[bind_group_layout],
|
bind_group_layouts: &[bind_group_layout, tile_bind_group_layout],
|
||||||
push_constant_ranges: &[],
|
push_constant_ranges: &[],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,7 +306,7 @@ pub fn create_railway_pipeline(
|
|||||||
entry_point: "fs_main",
|
entry_point: "fs_main",
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: *format,
|
format: *format,
|
||||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ impl RenderService {
|
|||||||
building_pipeline: create_colored_building_pipeline(device, format, camera_layout),
|
building_pipeline: create_colored_building_pipeline(device, format, camera_layout),
|
||||||
water_pipeline: create_water_pipeline(device, format, camera_layout, &tile_bind_group_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),
|
water_line_pipeline: create_water_line_pipeline(device, format, camera_layout),
|
||||||
railway_pipeline: create_railway_pipeline(device, format, camera_layout),
|
railway_pipeline: create_railway_pipeline(device, format, camera_layout, &tile_bind_group_layout),
|
||||||
motorway_outline: create_road_motorway_outline_pipeline(device, format, camera_layout),
|
motorway_outline: create_road_motorway_outline_pipeline(device, format, camera_layout),
|
||||||
motorway_fill: create_road_motorway_pipeline(device, format, camera_layout),
|
motorway_fill: create_road_motorway_pipeline(device, format, camera_layout),
|
||||||
primary_outline: create_road_primary_outline_pipeline(device, format, camera_layout),
|
primary_outline: create_road_primary_outline_pipeline(device, format, camera_layout),
|
||||||
@@ -86,7 +86,7 @@ impl RenderService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_tile_buffers(device: &wgpu::Device, state: &mut AppState, tile: (i32, i32, i32)) {
|
pub fn create_tile_buffers(device: &wgpu::Device, state: &mut AppState, tile: (i32, i32, i32), tile_bind_group_layout: &wgpu::BindGroupLayout) {
|
||||||
// Build vertex data for each feature type - roads use RoadVertex
|
// Build vertex data for each feature type - roads use RoadVertex
|
||||||
let mut road_motorway_vertex_data: Vec<RoadVertex> = Vec::new();
|
let mut road_motorway_vertex_data: Vec<RoadVertex> = Vec::new();
|
||||||
let mut road_primary_vertex_data: Vec<RoadVertex> = Vec::new();
|
let mut road_primary_vertex_data: Vec<RoadVertex> = Vec::new();
|
||||||
@@ -164,7 +164,6 @@ impl RenderService {
|
|||||||
Some("fire_station") | Some("courthouse") | Some("embassy")
|
Some("fire_station") | Some("courthouse") | Some("embassy")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assign color based on building type (light theme colors)
|
|
||||||
// Assign color based on building type (light theme colors)
|
// Assign color based on building type (light theme colors)
|
||||||
// Colors darkened for better visibility against #F5F4F0 background
|
// Colors darkened for better visibility against #F5F4F0 background
|
||||||
let color: [f32; 3] = if is_public_amenity {
|
let color: [f32; 3] = if is_public_amenity {
|
||||||
@@ -267,21 +266,43 @@ impl RenderService {
|
|||||||
|
|
||||||
// Process railways
|
// Process railways
|
||||||
if let Some(railways) = state.railways.get(&tile) {
|
if let Some(railways) = state.railways.get(&tile) {
|
||||||
|
// Calculate tile origin and scale for relative coordinates (re-calculated here for loop)
|
||||||
|
let (z, x, y) = tile;
|
||||||
|
let tile_count = 2_f64.powi(z);
|
||||||
|
let tile_size = 1.0 / tile_count;
|
||||||
|
let tile_origin_x = x as f64 * tile_size;
|
||||||
|
let tile_origin_y = y as f64 * tile_size;
|
||||||
|
|
||||||
for railway in railways {
|
for railway in railways {
|
||||||
let mut centers: Vec<[f32; 2]> = Vec::new();
|
let mut centers: Vec<[f32; 2]> = Vec::new();
|
||||||
|
|
||||||
|
// CRITICAL FIX: Match labels.rs logic - read as F32 (8 bytes per point)
|
||||||
|
// Previous logic used 16 bytes (f64), but if labels are working, data MUST be f32.
|
||||||
for chunk in railway.points.chunks(8) {
|
for chunk in railway.points.chunks(8) {
|
||||||
if chunk.len() < 8 { break; }
|
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]));
|
// Read as f32 then cast to f64 for projection precision
|
||||||
let (x, y) = project(lat as f64, lon as f64);
|
let lat = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4])) as f64;
|
||||||
centers.push([x as f32, y as f32]);
|
let lon = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4])) as f64;
|
||||||
|
|
||||||
|
// Project to global
|
||||||
|
let (gx, gy) = crate::geo::project_high_precision(lat, lon);
|
||||||
|
|
||||||
|
// Convert to tile-relative (0.0 - 1.0)
|
||||||
|
let rx = ((gx - tile_origin_x) / tile_size) as f32;
|
||||||
|
let ry = ((gy - tile_origin_y) / tile_size) as f32;
|
||||||
|
|
||||||
|
centers.push([rx, ry]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine color
|
||||||
|
// ... (rest of logic)
|
||||||
|
|
||||||
// Get color from tags (OSM uses 'colour', but also check 'color')
|
// Get color from tags (OSM uses 'colour', but also check 'color')
|
||||||
let color = railway.tags.get("colour")
|
let color = railway.tags.get("colour")
|
||||||
.or_else(|| railway.tags.get("color"))
|
.or_else(|| railway.tags.get("color"))
|
||||||
.and_then(|c| parse_hex_color(c))
|
.and_then(|c| parse_hex_color(c))
|
||||||
.unwrap_or([0.5, 0.5, 0.5]); // Default gray if no color
|
.unwrap_or([0.5, 0.5, 0.5]); // Default to grey if no color specified
|
||||||
|
|
||||||
// Determine railway type - only S-Bahn and U-Bahn, skip tram
|
// Determine railway type - only S-Bahn and U-Bahn, skip tram
|
||||||
// 0.0 = S-Bahn (solid line with white outline, wider)
|
// 0.0 = S-Bahn (solid line with white outline, wider)
|
||||||
@@ -392,21 +413,6 @@ impl RenderService {
|
|||||||
// Get RenderService to access tile_bind_group_layout
|
// Get RenderService to access tile_bind_group_layout
|
||||||
// We need to pass it from lib.rs or store it somewhere accessible
|
// We need to pass it from lib.rs or store it somewhere accessible
|
||||||
// For now, we'll recreate it (not ideal but functional)
|
// 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 {
|
let tile_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
layout: &tile_bind_group_layout,
|
layout: &tile_bind_group_layout,
|
||||||
|
|||||||
@@ -501,7 +501,13 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
if is_railway {
|
if is_railway {
|
||||||
let (first_lat, first_lon) = simplified_points[0];
|
let (first_lat, first_lon) = simplified_points[0];
|
||||||
railway_store.insert_way(id, tags.clone(), line_blob.clone(), first_lat, first_lon);
|
// Serialize as f64 for high precision
|
||||||
|
let mut railway_blob = Vec::with_capacity(simplified_points.len() * 16);
|
||||||
|
for (lat, lon) in &simplified_points {
|
||||||
|
railway_blob.extend_from_slice(&lat.to_le_bytes());
|
||||||
|
railway_blob.extend_from_slice(&lon.to_le_bytes());
|
||||||
|
}
|
||||||
|
railway_store.insert_way(id, tags.clone(), railway_blob, first_lat, first_lon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -704,14 +710,28 @@ async fn main() -> Result<()> {
|
|||||||
let (x, y) = TileService::lat_lon_to_tile(railway.first_lat, railway.first_lon, zoom);
|
let (x, y) = TileService::lat_lon_to_tile(railway.first_lat, railway.first_lon, zoom);
|
||||||
let zoom_i32 = zoom as i32;
|
let zoom_i32 = zoom as i32;
|
||||||
|
|
||||||
// Parse geometry from blob and generate railway mesh
|
// Parse geometry from blob (f64) and generate tile-relative railway mesh
|
||||||
let mut points: Vec<[f32; 2]> = Vec::new();
|
let mut points: Vec<[f32; 2]> = Vec::new();
|
||||||
for chunk in railway.points.chunks(8) {
|
|
||||||
if chunk.len() < 8 { break; }
|
// Calculate tile origin and scale
|
||||||
let lat = f32::from_le_bytes(chunk[0..4].try_into().unwrap_or([0u8; 4])) as f64;
|
let tile_count = 2_f64.powi(zoom as i32);
|
||||||
let lon = f32::from_le_bytes(chunk[4..8].try_into().unwrap_or([0u8; 4])) as f64;
|
let tile_size = 1.0 / tile_count;
|
||||||
let (x, y) = GeometryService::project(lat, lon);
|
let tile_origin_x = x as f64 * tile_size;
|
||||||
points.push([x, y]);
|
let tile_origin_y = y as f64 * tile_size;
|
||||||
|
|
||||||
|
for chunk in railway.points.chunks(16) {
|
||||||
|
if chunk.len() < 16 { break; }
|
||||||
|
let lat = f64::from_le_bytes(chunk[0..8].try_into().unwrap_or([0u8; 8]));
|
||||||
|
let lon = f64::from_le_bytes(chunk[8..16].try_into().unwrap_or([0u8; 8]));
|
||||||
|
|
||||||
|
// Project to Global
|
||||||
|
let (gx, gy) = GeometryService::project_high_precision(lat, lon);
|
||||||
|
|
||||||
|
// Convert to Tile-Relative (0.0 to 1.0)
|
||||||
|
let rx = ((gx as f64 - tile_origin_x) / tile_size) as f32;
|
||||||
|
let ry = ((gy as f64 - tile_origin_y) / tile_size) as f32;
|
||||||
|
|
||||||
|
points.push([rx, ry]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse color and rail type
|
// Parse color and rail type
|
||||||
@@ -733,7 +753,7 @@ async fn main() -> Result<()> {
|
|||||||
let rail_type_str = tags.get("railway").map(|s| s.as_str()).unwrap_or("rail");
|
let rail_type_str = tags.get("railway").map(|s| s.as_str()).unwrap_or("rail");
|
||||||
let rail_type: f32 = match rail_type_str {
|
let rail_type: f32 = match rail_type_str {
|
||||||
"subway" => 1.0,
|
"subway" => 1.0,
|
||||||
"tram" => 2.0,
|
// "tram" => 2.0, // Disabled
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl FilteringService {
|
|||||||
12 => {
|
12 => {
|
||||||
matches!(highway, Some("motorway" | "trunk" | "primary" | "secondary" | "tertiary" | "residential" | "unclassified" | "pedestrian" | "service" | "track")) || // Added minor roads
|
matches!(highway, Some("motorway" | "trunk" | "primary" | "secondary" | "tertiary" | "residential" | "unclassified" | "pedestrian" | "service" | "track")) || // Added minor roads
|
||||||
matches!(place, Some("city" | "town" | "village")) ||
|
matches!(place, Some("city" | "town" | "village")) ||
|
||||||
matches!(railway, Some("rail" | "subway" | "light_rail" | "narrow_gauge" | "tram")) || // Added tram
|
matches!(railway, Some("rail" | "subway" | "light_rail" | "narrow_gauge")) || // Removed tram
|
||||||
tags.contains_key("building") ||
|
tags.contains_key("building") ||
|
||||||
tags.contains_key("landuse") ||
|
tags.contains_key("landuse") ||
|
||||||
tags.contains_key("leisure") ||
|
tags.contains_key("leisure") ||
|
||||||
|
|||||||
@@ -102,4 +102,17 @@ impl GeometryService {
|
|||||||
|
|
||||||
(x, y)
|
(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// High precision Web Mercator Projection
|
||||||
|
/// Returns (x, y) in range [0.0, 1.0] for the whole world as f64
|
||||||
|
pub fn project_high_precision(lat: f64, lon: f64) -> (f64, f64) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
let x = if x.is_finite() { x.clamp(0.0, 1.0) } else { 0.5 };
|
||||||
|
let y = if y.is_finite() { y.clamp(0.0, 1.0) } else { 0.5 };
|
||||||
|
|
||||||
|
(x, y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ impl MeshGenerationService {
|
|||||||
let dx = p2[0] - p1[0];
|
let dx = p2[0] - p1[0];
|
||||||
let dy = p2[1] - p1[1];
|
let dy = p2[1] - p1[1];
|
||||||
let len = (dx * dx + dy * dy).sqrt();
|
let len = (dx * dx + dy * dy).sqrt();
|
||||||
if len < 0.000001 {
|
if len < 0.000000001 {
|
||||||
segment_normals.push([0.0, 0.0]);
|
segment_normals.push([0.0, 0.0]);
|
||||||
degenerate_count += 1;
|
degenerate_count += 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -293,7 +293,7 @@ impl MeshGenerationService {
|
|||||||
let dx = p2[0] - p1[0];
|
let dx = p2[0] - p1[0];
|
||||||
let dy = p2[1] - p1[1];
|
let dy = p2[1] - p1[1];
|
||||||
let len = (dx * dx + dy * dy).sqrt();
|
let len = (dx * dx + dy * dy).sqrt();
|
||||||
if len < 0.000001 {
|
if len < 0.000000001 {
|
||||||
segment_normals.push([0.0, 0.0]);
|
segment_normals.push([0.0, 0.0]);
|
||||||
} else {
|
} else {
|
||||||
segment_normals.push([-dy / len, dx / len]);
|
segment_normals.push([-dy / len, dx / len]);
|
||||||
|
|||||||
Reference in New Issue
Block a user