forked from 1b-agent/opencadstudio-upstream
feat(render): split mixed polyline bulge arcs into GPU analytical curves and tag benchmark
This commit is contained in:
1 parent
8922a5a25b
commit
d988a09987
2 files changed
+291
-35
No files matched your search
+252
-35
@@ -19,7 +19,7 @@ use acadrust::{CadDocument, EntityType, Handle};
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::scene::convert::acad_to_render::{convert, RenderObject};
|
||||
use crate::scene::model::wire_model::{SnapHint, WireModel};
|
||||
use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
|
||||
|
||||
/// Split an f64 offset-relative coordinate into the double-single (high, low)
|
||||
/// f32 pair the renderer consumes. `high + low ≈ value` to ~f64 precision; the
|
||||
@@ -202,6 +202,150 @@ pub(crate) fn points_to_ds(
|
||||
(high, low)
|
||||
}
|
||||
|
||||
fn split_mixed_polyline(
|
||||
tangent_geoms: &[TangentGeom],
|
||||
key_vertices: &[[f64; 3]],
|
||||
name: &str,
|
||||
color: [f32; 4],
|
||||
selected: bool,
|
||||
pattern_length: f32,
|
||||
pattern: [f32; 8],
|
||||
line_weight_px: f32,
|
||||
snap_pts: Vec<(glam::DVec3, SnapHint)>,
|
||||
point_marker: Option<crate::scene::model::wire_model::PointMarker>,
|
||||
plinegen: bool,
|
||||
) -> Vec<WireModel> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
// Emit each arc segment as an analytical wire (CircleGpu target)
|
||||
for tg in tangent_geoms {
|
||||
if matches!(tg, TangentGeom::Arc { .. }) {
|
||||
out.push(WireModel {
|
||||
bg_adapt: None,
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: name.to_string(),
|
||||
points: Vec::new(),
|
||||
points_low: Vec::new(),
|
||||
color,
|
||||
selected,
|
||||
pattern_length,
|
||||
pattern,
|
||||
line_weight_px,
|
||||
snap_pts: Vec::new(),
|
||||
tangent_geoms: vec![tg.clone()],
|
||||
aci: 0,
|
||||
key_vertices: Vec::new(),
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Collect straight lines into a line wire
|
||||
let mut straight_pts: Vec<[f64; 3]> = Vec::new();
|
||||
let mut straight_tangents: Vec<TangentGeom> = Vec::new();
|
||||
let mut last_end: Option<[f64; 3]> = None;
|
||||
|
||||
for (i, tg) in tangent_geoms.iter().enumerate() {
|
||||
if let TangentGeom::Line { p1, p2 } = tg {
|
||||
straight_tangents.push(tg.clone());
|
||||
let (p_start, p_end) = if i + 1 < key_vertices.len() {
|
||||
(key_vertices[i], key_vertices[i + 1])
|
||||
} else {
|
||||
(
|
||||
[p1[0] as f64, p1[1] as f64, p1[2] as f64],
|
||||
[p2[0] as f64, p2[1] as f64, p2[2] as f64],
|
||||
)
|
||||
};
|
||||
if !plinegen {
|
||||
if !straight_pts.is_empty() {
|
||||
straight_pts.push([f64::NAN; 3]);
|
||||
}
|
||||
straight_pts.push(p_start);
|
||||
straight_pts.push(p_end);
|
||||
} else {
|
||||
if let Some(prev) = last_end {
|
||||
if (prev[0] - p_start[0]).abs() < 1e-7
|
||||
&& (prev[1] - p_start[1]).abs() < 1e-7
|
||||
&& (prev[2] - p_start[2]).abs() < 1e-7
|
||||
{
|
||||
straight_pts.push(p_end);
|
||||
} else {
|
||||
straight_pts.push([f64::NAN; 3]);
|
||||
straight_pts.push(p_start);
|
||||
straight_pts.push(p_end);
|
||||
}
|
||||
} else {
|
||||
straight_pts.push(p_start);
|
||||
straight_pts.push(p_end);
|
||||
}
|
||||
last_end = Some(p_end);
|
||||
}
|
||||
} else {
|
||||
last_end = None;
|
||||
}
|
||||
}
|
||||
|
||||
let (line_pts, line_pts_low) = points_to_ds(straight_pts);
|
||||
if !line_pts.is_empty() {
|
||||
out.push(WireModel {
|
||||
bg_adapt: None,
|
||||
point_marker,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: name.to_string(),
|
||||
points: line_pts,
|
||||
points_low: line_pts_low,
|
||||
color,
|
||||
selected,
|
||||
pattern_length,
|
||||
pattern,
|
||||
line_weight_px,
|
||||
snap_pts,
|
||||
tangent_geoms: straight_tangents,
|
||||
aci: 0,
|
||||
key_vertices: key_vertices.to_vec(),
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
} else if let Some(first_arc) = out.first_mut() {
|
||||
first_arc.snap_pts = snap_pts;
|
||||
first_arc.key_vertices = key_vertices.to_vec();
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn point_cloud_wires(
|
||||
document: &CadDocument,
|
||||
handle: Handle,
|
||||
@@ -1569,40 +1713,71 @@ pub fn tessellate(
|
||||
};
|
||||
let point_marker =
|
||||
crate::entities::point::relative_marker_spec(entity, document);
|
||||
out.push(WireModel {
|
||||
bg_adapt: None,
|
||||
point_marker,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: polyline_band_width(entity, document.header.fill_mode),
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: name.clone(),
|
||||
points: local_pts,
|
||||
points_low: local_pts_low,
|
||||
color: edge_color,
|
||||
selected,
|
||||
pattern_length: edge_pattern_length,
|
||||
pattern: edge_pattern,
|
||||
line_weight_px,
|
||||
snap_pts: snap,
|
||||
tangent_geoms: tangents,
|
||||
aci: 0,
|
||||
key_vertices: keys,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
let has_arc = te
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|tg| matches!(tg, TangentGeom::Arc { .. }));
|
||||
let has_line = te
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|tg| matches!(tg, TangentGeom::Line { .. }));
|
||||
let is_thin_polyline = !is_thick_extrusion
|
||||
&& polyline_band_width(entity, document.header.fill_mode) <= 1e-9
|
||||
&& fill_tris.is_empty()
|
||||
&& pick_tris.is_empty()
|
||||
&& matches!(entity, EntityType::LwPolyline(_) | EntityType::Polyline2D(_));
|
||||
|
||||
if has_arc && has_line && is_thin_polyline {
|
||||
out.extend(split_mixed_polyline(
|
||||
&te.tangent_geoms,
|
||||
&keys,
|
||||
&name,
|
||||
edge_color,
|
||||
selected,
|
||||
edge_pattern_length,
|
||||
edge_pattern,
|
||||
line_weight_px,
|
||||
snap,
|
||||
point_marker,
|
||||
true,
|
||||
));
|
||||
} else {
|
||||
out.push(WireModel {
|
||||
bg_adapt: None,
|
||||
point_marker,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: Vec::new(),
|
||||
world_width: polyline_band_width(entity, document.header.fill_mode),
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: name.clone(),
|
||||
points: local_pts,
|
||||
points_low: local_pts_low,
|
||||
color: edge_color,
|
||||
selected,
|
||||
pattern_length: edge_pattern_length,
|
||||
pattern: edge_pattern,
|
||||
line_weight_px,
|
||||
snap_pts: snap,
|
||||
tangent_geoms: tangents,
|
||||
aci: 0,
|
||||
key_vertices: keys,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !fill_tris.is_empty() {
|
||||
@@ -1758,6 +1933,48 @@ pub fn tessellate(
|
||||
// A wide polyline with PLINEGEN=0 arrives here: same shader-band
|
||||
// treatment as the Contour arm, restarting the dash per segment.
|
||||
let (pick_tris, pick_tris_low) = points_to_ds(te.pick_tris);
|
||||
let is_thick_extrusion = matches!(
|
||||
entity,
|
||||
EntityType::LwPolyline(p) if p.thickness.abs() > 1e-10
|
||||
) || matches!(
|
||||
entity,
|
||||
EntityType::Polyline2D(p) if p.thickness.abs() > 1e-10
|
||||
);
|
||||
let is_thin_polyline = !is_thick_extrusion
|
||||
&& polyline_band_width(entity, document.header.fill_mode) <= 1e-9
|
||||
&& pick_tris.is_empty()
|
||||
&& matches!(entity, EntityType::LwPolyline(_) | EntityType::Polyline2D(_));
|
||||
let has_arc = te
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|tg| matches!(tg, TangentGeom::Arc { .. }));
|
||||
let has_line = te
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|tg| matches!(tg, TangentGeom::Line { .. }));
|
||||
|
||||
if has_arc && has_line && is_thin_polyline {
|
||||
let edge_color = if is_thick_extrusion {
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
} else {
|
||||
color
|
||||
};
|
||||
let point_marker =
|
||||
crate::entities::point::relative_marker_spec(entity, document);
|
||||
return split_mixed_polyline(
|
||||
&te.tangent_geoms,
|
||||
&key_vertices,
|
||||
&name,
|
||||
edge_color,
|
||||
selected,
|
||||
pattern_length,
|
||||
pattern,
|
||||
line_weight_px,
|
||||
snap_pts,
|
||||
point_marker,
|
||||
false,
|
||||
);
|
||||
}
|
||||
return vec![WireModel {
|
||||
bg_adapt: None,
|
||||
point_marker: None,
|
||||
|
||||
@@ -11400,6 +11400,45 @@ mod layout_cache_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_bulge_polyline_splits_into_analytical_arcs_and_straight_lines() {
|
||||
use acadrust::entities::{LwPolyline, LwVertex};
|
||||
use acadrust::types::Vector2;
|
||||
|
||||
let mut scene = Scene::new();
|
||||
let mut pline = LwPolyline::new();
|
||||
// Rounded slot: 2 straight lines and 2 semicircular ends (bulge = 1.0)
|
||||
// Vertex 0: (0, 0), bulge 1.0 (arc from 0,0 to 0,10)
|
||||
// Vertex 1: (0, 10), bulge 0.0 (straight from 0,10 to 50,10)
|
||||
// Vertex 2: (50, 10), bulge 1.0 (arc from 50,10 to 50,0)
|
||||
// Vertex 3: (50, 0), bulge 0.0 (straight from 50,0 to 0,0)
|
||||
let v0 = LwVertex::with_bulge(Vector2::new(0.0, 0.0), 1.0);
|
||||
let v1 = LwVertex::new(Vector2::new(0.0, 10.0));
|
||||
let v2 = LwVertex::with_bulge(Vector2::new(50.0, 10.0), 1.0);
|
||||
let v3 = LwVertex::new(Vector2::new(50.0, 0.0));
|
||||
pline.vertices = vec![v0, v1, v2, v3];
|
||||
pline.is_closed = true;
|
||||
|
||||
let _ = scene.add_entity(EntityType::LwPolyline(pline));
|
||||
|
||||
let camera = Camera::default();
|
||||
let wires = scene.model_tile_wires_arc(0, &camera, 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(),
|
||||
2,
|
||||
"Mixed polyline should have its 2 bulge arcs routed to analytical CircleGpu"
|
||||
);
|
||||
assert_eq!(
|
||||
partitioned.regular.len(),
|
||||
1,
|
||||
"Mixed polyline straight lines should form 1 regular wire"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "benchmark"]
|
||||
fn bench_analytical_rendering() {
|
||||
use std::time::Instant;
|
||||
use acadrust::entities::{Arc as AcadArc, Circle, Ellipse, LwPolyline, LwVertex};
|
||||
|
||||
Reference in new issue
Block a user