diff --git a/src/entities/common.rs b/src/entities/common.rs index 4c8c98f6c..1f837daca 100644 --- a/src/entities/common.rs +++ b/src/entities/common.rs @@ -702,6 +702,30 @@ pub fn parse_angle_deg(value: &str) -> Option { /// there is one obvious place to see that the maths moved out. pub use cadkernel::geom2d::BulgeArc; +/// Convert a 2D BulgeArc into a 3D TangentGeom::Arc with its world center, +/// plane axes, radius, and counter-clockwise start/end sweep angles. +pub fn bulge_arc_to_tangent( + arc: &BulgeArc, + to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64), + normal: (f64, f64, f64), +) -> crate::scene::model::wire_model::TangentGeom { + let (cwx, cwy, cwz) = to_wcs(arc.center[0], arc.center[1]); + let (ax, ay) = crate::scene::view::transform::ocs_axes(normal); + let (sa, ea) = if arc.sweep >= 0.0 { + (arc.start_angle, arc.end_angle) + } else { + (arc.end_angle, arc.start_angle) + }; + crate::scene::model::wire_model::TangentGeom::Arc { + center: [cwx, cwy, cwz], + axis_x: [ax.0, ax.1, ax.2], + axis_y: [ay.0, ay.1, ay.2], + radius: arc.radius, + start_angle: sa, + end_angle: ea, + } +} + /// Triangulate the solid bands a `wide_fills` returns into the flat WCS f64 /// triangle list `RenderEntity::pick_tris` carries, so a wide polyline is /// selectable across the band it draws and not just along its centreline. diff --git a/src/entities/lwpolyline.rs b/src/entities/lwpolyline.rs index 77a185326..8b125fe39 100644 --- a/src/entities/lwpolyline.rs +++ b/src/entities/lwpolyline.rs @@ -351,11 +351,7 @@ fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity { } else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge([ox0, oy0], [ox1, oy1], bulge) { - let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]); - tgs.push(TangentGeom::Circle { - center: [wcx as f32, wcy as f32, wcz as f32], - radius: arc.radius as f32, - }); + tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal)); for s in arc .tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE) .into_iter() @@ -407,11 +403,7 @@ fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity { [v1.location.x, v1.location.y], v0.bulge, ) { - let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]); - tgs.push(TangentGeom::Circle { - center: [wcx as f32, wcy as f32, wcz as f32], - radius: arc.radius as f32, - }); + tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal)); } } let (pts, widths) = crate::entities::common::tapered_band_points( @@ -470,11 +462,7 @@ fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity { } let (wx1, wy1, wz1) = to_wcs(ox1, oy1); kv.push([wx1, wy1, wz1]); - let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]); - tgs.push(TangentGeom::Circle { - center: [wcx as f32, wcy as f32, wcz as f32], - radius: arc.radius as f32, - }); + tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal)); } if i + 1 < seg_count { pts.push([f64::NAN; 3]); @@ -565,11 +553,8 @@ fn centerline_metadata( [end.location.x, end.location.y], start.bulge, ) { - let center = to_wcs(arc.center[0], arc.center[1]); - tangents.push(TangentGeom::Circle { - center: [center.0 as f32, center.1 as f32, center.2 as f32], - radius: arc.radius as f32, - }); + let normal = (pline.normal.x, pline.normal.y, pline.normal.z); + tangents.push(crate::entities::common::bulge_arc_to_tangent(&arc, to_wcs, normal)); } if index == 0 { key_vertices.push([p0.0, p0.1, p0.2]); diff --git a/src/entities/polyline.rs b/src/entities/polyline.rs index d9cd71404..de67a35dd 100644 --- a/src/entities/polyline.rs +++ b/src/entities/polyline.rs @@ -248,7 +248,7 @@ fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity { ); } let (tangent_geoms, key_vertices) = - centerline_metadata_2d(verts, pl.is_closed(), &to_wcs); + centerline_metadata_2d(verts, pl.is_closed(), &to_wcs, normal); return RenderEntity { pick_tris: Vec::new(), object: RenderObject::BoundaryLines { @@ -296,11 +296,7 @@ fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity { } else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge([ox0, oy0], [ox1, oy1], bulge) { - let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]); - tgs.push(TangentGeom::Circle { - center: [wcx as f32, wcy as f32, wcz as f32], - radius: arc.radius as f32, - }); + tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal)); for s in arc .tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE) .into_iter() @@ -378,11 +374,7 @@ fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity { [v1.location.x, v1.location.y], bulge, ) { - let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]); - tangents.push(TangentGeom::Circle { - center: [wcx as f32, wcy as f32, wcz as f32], - radius: arc.radius as f32, - }); + tangents.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal)); } if i == 0 { @@ -464,6 +456,7 @@ fn centerline_metadata_2d( verts: &[acadrust::entities::Vertex2D], closed: bool, to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64), + normal: (f64, f64, f64), ) -> (Vec, Vec<[f64; 3]>) { let count = verts.len(); let segment_count = if closed { @@ -488,11 +481,7 @@ fn centerline_metadata_2d( [end.location.x, end.location.y], start.bulge, ) { - let center = to_wcs(arc.center[0], arc.center[1]); - tangents.push(TangentGeom::Circle { - center: [center.0 as f32, center.1 as f32, center.2 as f32], - radius: arc.radius as f32, - }); + tangents.push(crate::entities::common::bulge_arc_to_tangent(&arc, to_wcs, normal)); } if index == 0 { key_vertices.push([p0.0, p0.1, p0.2]); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 2d06cabcb..2d90e9655 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -11325,4 +11325,37 @@ mod layout_cache_tests { partitioned.circle_instances.len() ); } + + #[test] + fn single_bulge_polyline_extracts_as_analytical_gpu_arc() { + let mut s = Scene::new(); + let mut pline = acadrust::entities::LwPolyline::new(); + pline.vertices = vec![ + acadrust::entities::LwVertex { + location: acadrust::types::Vector2::new(0.0, 0.0), + bulge: 1.0, // semicircle + start_width: 0.0, + end_width: 0.0, + vertex_id: 0, + }, + acadrust::entities::LwVertex { + location: acadrust::types::Vector2::new(100.0, 0.0), + bulge: 0.0, + start_width: 0.0, + end_width: 0.0, + vertex_id: 0, + }, + ]; + let _ = s.add_entity(EntityType::LwPolyline(pline)); + + let wires = s.model_tile_wires_arc(0, &Camera::default(), 1.0, 1000.0); + let depths = rustc_hash::FxHashMap::default(); + let partitioned = crate::scene::pipeline::wire_arena::partition_wires(&wires, &depths); + + assert_eq!( + partitioned.circle_instances.len(), + 1, + "Single bulge arc polyline must be extracted as analytical GPU arc instance" + ); + } }