diff --git a/src/entities/lwpolyline.rs b/src/entities/lwpolyline.rs index f03a68fe8..dc41ffce8 100644 --- a/src/entities/lwpolyline.rs +++ b/src/entities/lwpolyline.rs @@ -64,6 +64,51 @@ fn bulge_from_midpoint(p0: [f64; 2], p1: [f64; 2], mid: [f64; 2]) -> Option Some(if cross >= 0.0 { bulge } else { -bulge }) } +/// Tessellate a thick polyline segment list into NaN-separated Lines geometry. +/// Shared by LwPolyline and Polyline2D thickness paths. +fn thick_segments( + seg_data: &[(f64, f64, f64, f64)], // (x0, y0, x1, y1) per seg — or use run of (x,y,bulge) + path_pts: &[[f32; 3]], + thickness: f64, + normal: (f64, f64, f64), + key_verts: Vec<[f32; 3]>, + tangents: Vec, +) -> TruckEntity { + let (nx, ny, nz) = normal; + let t = thickness; + let off = |p: [f32; 3]| -> [f32; 3] { + [(p[0] as f64 + t * nx) as f32, (p[1] as f64 + t * ny) as f32, (p[2] as f64 + t * nz) as f32] + }; + let mut pts: Vec<[f32; 3]> = Vec::with_capacity(path_pts.len() * 2 + seg_data.len() * 3 + 4); + // Bottom path + pts.extend_from_slice(path_pts); + pts.push([f32::NAN; 3]); + // Top path + for &p in path_pts { + pts.push(off(p)); + } + // Walls at each vertex (seg_data.0/.1 = start x/y of each seg, last seg appends its end too) + if !seg_data.is_empty() { + pts.push([f32::NAN; 3]); + for (k, &(x0, y0, _x1, _y1)) in seg_data.iter().enumerate() { + let pb = key_verts[k]; + let _ = (x0, y0); // key_verts already has correct WCS + pts.push(pb); + pts.push(off(pb)); + if k + 1 < seg_data.len() { + pts.push([f32::NAN; 3]); + } + } + // Last wall at the final vertex + if let Some(&last) = key_verts.last() { + pts.push([f32::NAN; 3]); + pts.push(last); + pts.push(off(last)); + } + } + TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tangents, key_vertices: key_verts } +} + fn to_truck(pline: &LwPolyline) -> TruckEntity { let verts = &pline.vertices; if verts.is_empty() { @@ -92,6 +137,53 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity { Point3::new(wx, wy, wz) }; + if pline.thickness.abs() > 1e-10 { + let mut path: Vec<[f32; 3]> = Vec::new(); + let mut kv: Vec<[f32; 3]> = Vec::new(); + let mut tgs: Vec = Vec::new(); + let mut seg_data: Vec<(f64, f64, f64, f64)> = Vec::new(); + // First vertex + let (w0x, w0y, w0z) = to_wcs(verts[0].location.x, verts[0].location.y); + path.push([w0x as f32, w0y as f32, w0z as f32]); + kv.push([w0x as f32, w0y as f32, w0z as f32]); + for i in 0..seg_count { + let va = &verts[i]; + let vb = &verts[(i + 1) % count]; + let (ox0, oy0) = (va.location.x, va.location.y); + let (ox1, oy1) = (vb.location.x, vb.location.y); + let bulge = va.bulge; + if bulge.abs() < 1e-9 { + let (wx, wy, wz) = to_wcs(ox1, oy1); + path.push([wx as f32, wy as f32, wz as f32]); + tgs.push(TangentGeom::Line { p1: path[path.len()-2], p2: *path.last().unwrap() }); + } else { + let angle = 4.0 * bulge.atan(); + let dx = ox1 - ox0; let dy = oy1 - oy0; + let d = (dx * dx + dy * dy).sqrt().max(1e-12); + let r = (d / 2.0) / (angle / 2.0).sin().abs(); + let mx = (ox0 + ox1) * 0.5; let my = (oy0 + oy1) * 0.5; + let px = -dy / d; let py = dx / d; + let ss = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 }; + let h = r - (r * r - d * d / 4.0).max(0.0).sqrt(); + let ocx = mx + ss * px * (r - h); let ocy = my + ss * py * (r - h); + let a0 = (oy0 - ocy).atan2(ox0 - ocx); + let mut a1 = (oy1 - ocy).atan2(ox1 - ocx); + if bulge > 0.0 { if a1 < a0 { a1 += TAU; } } else { if a1 > a0 { a1 -= TAU; } } + let (wcx, wcy, wcz) = to_wcs(ocx, ocy); + tgs.push(TangentGeom::Circle { center: [wcx as f32, wcy as f32, wcz as f32], radius: r as f32 }); + for j in 1..=16usize { + let a = a0 + (a1 - a0) * (j as f64 / 16.0); + let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin()); + path.push([wx as f32, wy as f32, wz as f32]); + } + } + let (wbx, wby, wbz) = to_wcs(ox1, oy1); + kv.push([wbx as f32, wby as f32, wbz as f32]); + seg_data.push((ox0, oy0, ox1, oy1)); + } + return thick_segments(&seg_data, &path, pline.thickness, normal, kv, tgs); + } + for i in 0..seg_count { let v0 = &verts[i]; let v1 = &verts[(i + 1) % count]; diff --git a/src/entities/polyline.rs b/src/entities/polyline.rs index bc239ffb2..06920c547 100644 --- a/src/entities/polyline.rs +++ b/src/entities/polyline.rs @@ -146,6 +146,67 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity { Point3::new(wx, wy, wz) }; + if pl.thickness.abs() > 1e-10 { + let (nx, ny, nz) = normal; + let t = pl.thickness; + let off = |p: [f32; 3]| -> [f32; 3] { + [(p[0] as f64 + t * nx) as f32, (p[1] as f64 + t * ny) as f32, (p[2] as f64 + t * nz) as f32] + }; + let mut path: Vec<[f32; 3]> = Vec::new(); + let mut kv: Vec<[f32; 3]> = Vec::new(); + let mut tgs: Vec = Vec::new(); + let (w0x, w0y, w0z) = to_wcs(verts[0].location.x, verts[0].location.y); + path.push([w0x as f32, w0y as f32, w0z as f32]); + kv.push([w0x as f32, w0y as f32, w0z as f32]); + for i in 0..seg_count { + let va = &verts[i]; + let vb = &verts[(i + 1) % count]; + let (ox0, oy0) = (va.location.x, va.location.y); + let (ox1, oy1) = (vb.location.x, vb.location.y); + let bulge = va.bulge; + if bulge.abs() < 1e-9 { + let (wx, wy, wz) = to_wcs(ox1, oy1); + path.push([wx as f32, wy as f32, wz as f32]); + tgs.push(TangentGeom::Line { p1: path[path.len()-2], p2: *path.last().unwrap() }); + } else { + let angle = 4.0 * bulge.atan(); + let dx = ox1 - ox0; let dy = oy1 - oy0; + let d = (dx * dx + dy * dy).sqrt().max(1e-12); + let r = (d / 2.0) / (angle / 2.0).sin().abs(); + let mx = (ox0 + ox1) * 0.5; let my = (oy0 + oy1) * 0.5; + let px = -dy / d; + let ss = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 }; + let h = r - (r * r - d * d / 4.0).max(0.0).sqrt(); + let ocx = mx + ss * px * (r - h); + let ocy = my + ss * (dx / d) * (r - h); + let a0 = (oy0 - ocy).atan2(ox0 - ocx); + let mut a1 = (oy1 - ocy).atan2(ox1 - ocx); + if bulge > 0.0 { if a1 < a0 { a1 += TAU; } } else { if a1 > a0 { a1 -= TAU; } } + let (wcx, wcy, wcz) = to_wcs(ocx, ocy); + tgs.push(TangentGeom::Circle { center: [wcx as f32, wcy as f32, wcz as f32], radius: r as f32 }); + for j in 1..=16usize { + let a = a0 + (a1 - a0) * (j as f64 / 16.0); + let (wx, wy, wz) = to_wcs(ocx + r * a.cos(), ocy + r * a.sin()); + path.push([wx as f32, wy as f32, wz as f32]); + } + } + let (wbx, wby, wbz) = to_wcs(ox1, oy1); + kv.push([wbx as f32, wby as f32, wbz as f32]); + } + let mut pts: Vec<[f32; 3]> = Vec::with_capacity(path.len() * 2 + kv.len() * 3 + 4); + pts.extend_from_slice(&path); + pts.push([f32::NAN; 3]); + for &p in &path { pts.push(off(p)); } + if !kv.is_empty() { + pts.push([f32::NAN; 3]); + for (i, &pb) in kv.iter().enumerate() { + pts.push(pb); pts.push(off(pb)); + if i + 1 < kv.len() { pts.push([f32::NAN; 3]); } + } + } + return TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tgs, key_vertices: kv }; + } + for i in 0..seg_count { let v0 = &verts[i]; let v1 = &verts[(i + 1) % count];